Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: PHP: periyttäminen kaataa sivun

Sivun loppuun

Ripe [25.03.2013 21:23:38]

#

Osaako joku kertoa, miksi näin tapahtuu: kun luokka Model perii luokan Db, ja sitten Modelin __construct-funktiossa kutsun Db:n konstruktoria tyyliin parent::__construct(), selain näyttää virheen, että ei saada yhteyttä sivustoon. Kun taas laitan Db-luokan Modelin jäsenmuuttujaksi, sivu latautuu oikein. Tässä vielä koodit:

<?php
// Model periytyy luokasta Db, ei toimi
class Model extends Db {
	function __construct($str, $user, $pass){
		parent::__construct($str, $user, $pass);
	}
}

// Db on Modelin jäsenmuuttujana, toimii
class Model {
	function __construct($str, $user, $pass){
		$this->db = new Db($str, $user, $pass);
	}
}

Itse en keksi tälle järjellistä selitystä, sillä kuulostaa järjettömältä, ettei voi periyttää luokkia kunnolla, mutta ehkä joku Php:tä osaava osaa valaista asiaa.

Metabolix [25.03.2013 21:30:59]

#

Molempien tapojen pitäisi toimia. Ongelman syy ei mitenkään selviä tuosta koodista. Sinun pitäisi selvittää, mikä virheilmoitus PHP:ssä tulee. Eräs mahdollinen syy on, että Db-luokkaa ei ole määritelty ajoissa; ensimmäisellä tavalla Db-luokka pitää määritellä ennen Model-luokkaa, kun taas jälkimmäisessä riittää, että se on määritelty ennen olion luomista.

Ripe [25.03.2013 21:36:26]

#

Ongelma ilmenee vain, kun vaihdan periyttämisen jäsenmuuttujaksi. Ja Db-luokka on määritelty ennen Model-luokkaa. En tiedä virheilmoituksista, selain (firefox) sanoo, että "Yhteys palvelimeen alustettiin kesken latauksen". En viitsisi kaikkea koodia tänne laittaa, koska sitä on muutaman tiedoston verran.
Ja sitähän minäkin, että molempien tapojen pitäisi toimia, siksi ihmettelenkin, miksei toimi.
Lisäys: php:n loki ei sano asiasta yhtään mitään.

Metabolix [25.03.2013 22:10:10]

#

Tulevatko muut virheilmoitukset normaalisti lokiin? Toki on mahdollista joskin epätodennäköistä, että PHP:ssä on bugi ja se kaatuu ilmoittamatta mitään; silloinkin asiasta usein löytyy maininta edes jostain palvelimen lokitiedostosta. Jos näin on (ja muutenkin), kannattaa päivittää PHP ja HTTP-palvelin.

Yksinkertainen tapa debugata koodia on lisätä die-rivejä. Lisää ensin die-rivi ennen Model-luokkaa; jos se toimii, siirrä rivi __construct-funktioon; jatka näin, kunnes saat haarukoitua koodista ensimmäisen kohdan, jossa die-viesti ei enää näy.

Ripe [26.03.2013 07:45:02]

#

Muut virheilmoitukset tulevat aivan normaalisti php:n lokiin. Mutta, apachen lokeista löytyy tällaista:

[Tue Mar 26 07:09:43.825233 2013] [mpm_winnt:notice] [pid 5084:tid 252] AH00456: Server built: May 13 2012 14:10:15
[Tue Mar 26 07:09:43.825233 2013] [core:notice] [pid 5084:tid 252] AH00094: Command line: 'apache\\bin\\httpd.exe -d C:/xampp/apache -f conf\\httpd.conf'
[Tue Mar 26 07:09:43.825233 2013] [mpm_winnt:notice] [pid 5084:tid 252] AH00418: Parent: Created child process 9200
[Tue Mar 26 07:09:47.850040 2013] [ssl:warn] [pid 9200:tid 264] AH01873: Init: Session Cache is not configured [hint: SSLSessionCache]
[Tue Mar 26 07:09:48.006040 2013] [mpm_winnt:notice] [pid 9200:tid 264] AH00354: Child: Starting 150 worker threads.
[Tue Mar 26 07:09:49.191642 2013] [mpm_winnt:notice] [pid 5084:tid 252] AH00428: Parent: child process exited with status 3221225725 -- Restarting.
[Tue Mar 26 07:09:50.829645 2013] [ssl:warn] [pid 5084:tid 252] AH01873: Init: Session Cache is not configured [hint: SSLSessionCache]
[Tue Mar 26 07:09:50.985645 2013] [mpm_winnt:notice] [pid 5084:tid 252] AH00455: Apache/2.4.2 (Win32) OpenSSL/1.0.1c PHP/5.4.4 configured -- resuming normal operations
[Tue Mar 26 07:09:50.985645 2013] [mpm_winnt:notice] [pid 5084:tid 252] AH00456: Server built: May 13 2012 14:10:15
...

Tuossa oli vain pätkä lokista, mutta siinä toistuu sama kaava, joka mielestäni viittaa php:n kaatumiseen.
Ja kun testasin die-llä "debugata", jos die on ennen tuota parent::__construct-riviä, dien viesti näkyy, mutta jos die on tuon rivin jälkeen, selain ilmoittaa virheen, ettei saada yhteyttä palvelimeen.

Metabolix [26.03.2013 14:23:17]

#

Lokitiedoista näkyy, että käytät aika vanhaa versiota PHP:stä. Kannattaa päivittää, paljon bugeja on korjattu.

Jos päivitys ei auta, jatka samanlaista die-debuggausta Db-luokan sisään ja pidemmällekin, kunnes löydät perimmäisen kaatumispaikan. Tulosta sitten rivillä käytettävät muuttujat vaikka funktiolla var_dump.

Ripe [26.03.2013 15:52:59]

#

"Palvelimeni" versio on tosiaan aika vanha, noin vuoden ikäinen xampp. Ja kiitos noista ohjeista, yritän niitä.

Ripe [26.03.2013 18:53:55]

#

Päivitin palvelimeni ja myös php:n versioon 5.4.7. Ja sama ongelma jatkuu. En kyllä ymmärrä mikä on ongelmana.

Lisäys: Noniin, ongelma on ratkaistu, ainakin jotenkin. Vaihtamalla rivin call_user_func_array(array($this, 'parent::__construct'), $args); riviksi parent::__construct($args) luokan Db konstruktorissa, homma toimii.

Metabolix [26.03.2013 21:48:17]

#

Ripe kirjoitti:

Päivitin palvelimeni ja myös php:n versioon 5.4.7.

No miksi siihen päivitit, kun uusin versio on 5.4.13?

Ripe kirjoitti:

Vaihtamalla rivin call_user_func_array(array($this, 'parent::__construct'), $args); riviksi parent::__construct($args) luokan Db konstruktorissa, homma toimii.

Ongelma on siis siinä, että Db-luokka on tehty väärin eikä sitä voi periä: funktiokutsu jää ikuiseen rekursioon (ja kaataa PHP:n), koska $this-olion parent onkin aina Db eli kutsutaan aina vain funktiota Db::__construct. Luokassa pitäisi olla final-sana, jotta virhettä ei voisi vahingossa tehdä, tai luokka olisi pitänyt tehdä oikein.

Oikea ratkaisu on vaihtaa funktiokutsuun parent-sanan tilalle get_parent_class(__CLASS__), jotta saadaan Db-luokan parent-luokka.

call_user_func_array(array($this, get_parent_class(__CLASS__).'::__construct'), $args);

Oma ratkaisusi (parent::__construct($args)) "toimii" tuurilla. Annat argumenttina vain taulukon arvoista (funktio(array(1,2,3))), vaikka pitäisi antaa erillisiä arvoja (funktio(1,2,3)). Ehkä funktio ei vain käytä parametrejaan. Voisit kyllä antaa tarvittavat argumentit yksitellen, jolloin koodi olisi oikein.

Ripe [27.03.2013 08:01:03]

#

Edellinen viestini taitaa kaivata lisäselvennystä.

Metabolix kirjoitti:

No miksi siihen päivitit, kun uusin versio on 5.4.13?

Päivitin siihen, koska käytän XAMPP:ia, kuten aiemmin mainitsin, ja php:n versio 5.4.7 sisältyy XAMPP:n uusimpaan versioon. Tokihan voisin kaiken (apache, php, mysql ...) asentaa erikseen, mutta näin windowsin kanssa pelatessa on helpompi vain asentaa XAMPP.

Metabolix kirjoitti:

Ongelma on siis siinä, että Db-luokka on tehty väärin eikä sitä voi periä: funktiokutsu jää ikuiseen rekursioon (ja kaataa PHP:n), koska $this-olion parent onkin aina Db eli kutsutaan aina vain funktiota Db::__construct. Luokassa pitäisi olla final-sana, jotta virhettä ei voisi vahingossa tehdä, tai luokka olisi pitänyt tehdä oikein.

Miten Db-olion parent voi olla itse Db? Ja voisitko kertoa, miten luokka olisi oikein tehty. En ole kovin hyvä php:n kanssa, olen käyttänyt sitä melko vähän, joten suurimmaksi osaksi vasta opettelen sen käyttöä.

Metabolix kirjoitti:

Oma ratkaisusi (parent::__construct($args)) "toimii" tuurilla. Annat argumenttina vain taulukon arvoista (funktio(array(1,2,3))), vaikka pitäisi antaa erillisiä arvoja (funktio(1,2,3)). Ehkä funktio ei vain käytä parametrejaan. Voisit kyllä antaa tarvittavat argumentit yksitellen, jolloin koodi olisi oikein.

Anteeksi epäselvyys, kirjoitin nuo suoraan tänne, enkä kopioinut niitä koodistani. Oikeasti annan tuolle parametriksi kolme erillistä muuttujaa, enkä taulukkoa.
Tässä vielä luokkien koodit, toivottavasti selventää asiaa. Olen myös kiinnostunut tietämään, mikä on parempi tapa tehdä tämä.

<?php
// models/Db.php
class Db extends PDO {
	function __construct($str, $user, $pass){
		parent::__construct($str, $user, $pass);

		$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->exec('SET CHARACTER SET utf8');
	}
}

// models/postModel.php
class postModel extends Db {
	function __construct(){
		call_user_func_array(array($this, 'parent::__construct'), func_get_args());
	}
}

// controllers/indexController.php
class indexController {
	function __construct(){
		$this->post = new postModel("mysql:host=localhost;dbname=*", "user", "password");
	}

	function index_action(){
		return array("messages" => $this->post->findByUserId($_SESSION["uid"]));
		// $_SESSION["uid"]-muuttujaan ei kannata kiinnittää huomiota, käytän sitä kyllä turvallisesti ja oikein
	}
}

// index.php:ssä require:n nuo luokat ja teen vähän kaikkea muutakin

Ja vielä kiitos Metabolixille kaikesta avusta.

Metabolix [27.03.2013 11:58:20]

#

Ripe kirjoitti:

Miten Db-olion parent voi olla itse Db? Ja voisitko kertoa, miten luokka olisi oikein tehty. En ole kovin hyvä php:n kanssa, olen käyttänyt sitä melko vähän, joten suurimmaksi osaksi vasta opettelen sen käyttöä.

Koodissa $this ei ole Db-olio vaan Model-olio. Funktio call_user_func ei erota, missä luokassa kutsu tapahtuu, vaan se katsoo olion todellista tyyppiä. Siksi se kutsuu Model-luokan metodia parent::__construct, joka on siis sama kuin Db::__construct. Selvensikö tämä riittävästi?

Kuten jo sanoin, oikeita ratkaisuja luokassa ovat suora parent::__construct ilman call_user_func-kikkailuja, kuten sinä teit, tai näyttämäni get_parent_class-temppu, joka vastaa tarkemmin alkuperäistä versiota. (Jälkimmäisen voisi yhtä hyvin tehdä vain vaihtamalla alkuperäiseen riviin funktion nimeksi 'PDO::__construct', mutta jos joskus kantaluokka vaihtuukin, se pitää muistaa vaihtaa erikseen myös tähän.)

Alkuperäisessä koodissa (ja minun ratkaisussani) on mahdollista antaa myös neljäs arvo, PDO:n $driver_options, vaikka sitä ei mainita Db-luokassa. Mielestäni tämä ominaisuus on kuitenkin hämäävä, ilmeisesti koodissa dokumentoimaton ja virhetilanteessa hankala löytää. Itse käyttäisin ehdottomasti suoraan parent::__construct-kutsua, jotta on täysin selvää, mitä arvoja PDO:lle annetaan.

Ratkaisit siis itse jo ongelman aivan oikein, kuvauksesi vain oli kirjaimellisesti tulkittuna väärä.

Tukki [27.03.2013 12:42:02]

#

Mietin tätä ongelmaa ja eteen tuli omituisuus jolle en keksi oiken järkevää selitystä enkä löydä vastausta php:n manuaalistakaan. Osaisiko joku kertoa järkevän selityksen miksi näin voi tehdä?

class B1 {
  public function b($b) {
    echo "B1\n";
  }
}
class B2 extends B1 {
  public function b($b, $c) {
    echo "B2\n";
  }
}

Eli perivä luokka B2 muuttaa metodin b määrittelyä niin ettei sitä ole enää mahdollista kutsua parent-luokassa määritellyillä parametreilla. Tämä vaikuttaa erityisen epäintuitiiviselta jos minulla on metodi, joka ottaa parametrinaan B1-luokan olion näin:

class F {
  public static function m(B1 $b) {
    $b->b(1);
  }
}

Voin antaa tälle metodille F::m(B1 $b) aivan ymmärrettävästi parametrina luokan B2 olion, mutta saan varoituksen kutsussa $b->b(1); koska parametreja annetaan vain yksi. Ei vaikuta minusta kovin intuitiiviselta, koska type hintin pitäisi huolehtia että parametri on B1 luokan olio (niinkuin onkin) ja silloin pitäisi olla turvallista kutsua B1-luokassa määriteltyjä metodeja siellä määritellyin parametrein.

Kuitenkin vastaava esimerkki, jossa parent-luokka ja metodi ovat abstrakteja antaa odotetusti ja dokumentoidusti fatal errorin:

abstract class A1 {
  abstract public function a($a);
}
class A2 extends A1 {
  public function a($a, $b) {
  }
}

Testattu PHP 5.3.2:lla

Ripe [27.03.2013 13:23:07]

#

Metabolix kirjoitti:

Ratkaisit siis itse jo ongelman aivan oikein, kuvauksesi vain oli kirjaimellisesti tulkittuna väärä.

Kieltämättä selitin ongelman hieman hankalan kuuloisesti.

The Alchemist [27.03.2013 14:09:07]

#

Tukki kirjoitti:

Mietin tätä ongelmaa ja eteen tuli omituisuus jolle en keksi oiken järkevää selitystä enkä löydä vastausta php:n manuaalistakaan. Osaisiko joku kertoa järkevän selityksen miksi näin voi tehdä?

class B1 {
  public function b($b) {
    echo "B1\n";
  }
}
class B2 extends B1 {
  public function b($b, $c) {
    echo "B2\n";
  }
}

Laitahan virheraportointi oikeasti päälle.

// PHP 5.3
error_reporting(E_ALL | E_STRICT);

// PHP 5.4
error_reporting(E_ALL);

lainaus:

PHP Strict Standards: Declaration of B2::b() should be compatible with B1::b($b)

Metabolix [27.03.2013 14:31:09]

#

Mainittakoon vielä, että vaikka tavallisen jäsenfunktion parametrien pitää pysyä samanlaisina, konstruktorin parametrit voivat olla erilaiset.

Tukki [27.03.2013 14:51:39]

#

The Alchemist kirjoitti:

Laitahan virheraportointi oikeasti päälle.

On. Tässä testikoodini kokonaisuudessaan:

<?php
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors',1);
header("content-type: text/plain; charset=\"utf-8\"");


class B1 {
  public function b($b) {
    echo "B1\n";
  }
}
class B2 extends B1 {
  public function b($b, $c) {
    echo "B2\n";
  }
}

class F {
  public static function m(B1 $b) {
    $b->b(1);
  }
}

echo phpversion()."\n";

foreach(array(new B1(), new B2()) as $b) {
  F::m($b);
}

ja tässä tuloste:

5.3.2-1ubuntu4.18
B1

Warning: Missing argument 2 for B2::b(), called in /home/username/projects/phptests/info.php on line 20 and defined in /home/username/projects/phptests/info.php on line 13

Call Stack:
    0.0003     657520   1. {main}() /home/username/projects/phptests/info.php:0
    0.0003     659984   2. F::m() /home/username/projects/phptests/info.php:27
    0.0003     660064   3. B2->b() /home/username/projects/phptests/info.php:20

B2

Voisiko tähän olla tullut muutos PHP-versioni jälkeen tai voisikohan olla jokin muu PHP:n asetus, joka estää tuon varoituksen näkymisen?

Metabolix [27.03.2013 15:04:27]

#

Tukki: PHP:n muutoslokin mukaan tarkistus on ollut PHP 5:ssä alusta asti. Koodisi ei näytä ilmoitusta, koska PHP käsittelee samassa lohkossa olevat luokat ja funktiot aina heti lohkon alussa eli tässä tapauksessa heti tiedoston alussa, jolloin virheilmoitukset eivät vielä ole käytössä. Voit kuitenkin lisätä luokkien ympärille ylimääräisen if-lauseen (if(1){}), niin PHP käsittelee luokat vasta siinä vaiheessa ja ilmoitukset näkyvät. Parempi ratkaisu on laittaa ilmoitukset käyttöön palvelimen asetuksista.

qeijo [27.03.2013 15:11:30]

#

Nimimerkki kirjoitti:

Voin antaa tälle metodille F::m(B1 $b) aivan ymmärrettävästi parametrina luokan B2 olion, mutta saan varoituksen kutsussa $b->b(1); koska parametreja annetaan vain yksi. Ei vaikuta minusta kovin intuitiiviselta, koska type hintin pitäisi huolehtia että parametri on B1 luokan olio (niinkuin onkin) ja silloin pitäisi olla turvallista kutsua B1-luokassa määriteltyjä metodeja siellä määritellyin parametrein.

Ei, type hint toimii aivan oikein, B2 on myös B1, olet ylikirjoittanut b - metodin, joten mielestäni se on aika loogista että tulee herja. Ei type hint tee mitään maagista castausta annetulle parametrille...

Tukki [27.03.2013 15:31:33]

#

Joops, tuo PHP Strict Standards-varoituksen näkymättömyys johtui tosiaan tuosta että asetan E_STRICT-varoitukset päälle vasta ajonaikaisesti ja "virhe" tapahtuu jo käännösaikana. Tuolla on keskustelua asiasta.

Silti ihmettelen että mikä logiikka siinä on että abstraktin parent-luokan tapauksessa tulee fatal-error (eli koodia ei voi ajaa ollenkaan) mutta jos parent on tavallinen luokka niin tulee vain E_STRICT-huomautus, joka manuaalin mukaan on vain suositus muuttaa koodia, ei virhe. Nuo E_STRICT-ilmoitukset varmaan jää monilla myös aika monesti kokonaan huomaamatta niinkuin minullakin meinasi käydä.

qeijo kirjoitti:

Ei, type hint toimii aivan oikein, koska B2 on myös B1, ja olet ylikirjoittanut b - metodin, joten mielestäni se on aika loogista että tulee herja. Ei type hint tee mitään maagista castausta annetulle parametrille...

Ymmärsit ehkä vääärin mitä tarkoitin. Type hint toimii ihan loogisesti minustakin ja myös se on loogista että kutsu $b->b(1); kutsuu B2-luokan metodia b. Epäloogista minusta on se että B2-luokan metodi b voi ylipäänsä määritellä erilaiset pakolliset parametrit kuin parent-luokassa on määritelty. Tuossa F-luokan metodissa m pitäisi voida käsitellä tuota parametria b aivan kuin se olisi B1-luokan olio, riippumatta siitä minkä luokan olio se todellisuudessa on. Ei oikein kuulosta järkevästä että type hintattujen olioparametrien kohdalla pitäisi silti tarkistaa olion todellinen tyyppi ennen jokaista metodikutsua, jotta voisi olla varma mitä parametreja metodi vaatii. Koko perinnän ajatus häviää aika pitkälle tuossa, jos en voi käsitellä olioita niin kuin ne olisivat parent-luokkiensa olioita.

Lisäys:

Löysinpäs kohdan manuaalista jossa tuosta mainitaan. Tuolla sanotaan "When overriding methods, the parameter signature should remain the same or PHP will generate an E_STRICT level error" eli "should", ei "must". Tuolla kerrotaan E_STRICT-viesteistä seuraavaa: "STRICT messages provide suggestions that can help ensure the best interoperability and forward compatibility of your code" eli se ei ole varsinaisesti virhe vaan suositus toimia toisin.

Onko tämä E_STRICT jonkun muun mielestä tässä aivan liian lievä virhetaso?

qeijo [28.03.2013 12:11:14]

#

Tuo on kieltämättä aika erikoista. Kun siinä ei varsinaisesti tehdä metodin ylikirjoitus, vaan ikään kuin kuormitetaan peritty metodi, mutta ei kuitenkaan...

class BaseClass {

    public function hello() {
        print 'Hello!';
    }

}

class DerivedClass extends BaseClass {

    public function hello($msg) {
        print $msg;
    }
}

class RandomClass {

    private $derivedClass;

    public function __construct(BaseClass $derivedClass) {
        $this->derivedClass = $derivedClass;
    }

    public function sayHello() {
        $this->derivedClass->hello();
    }
}

$baseClass = new BaseClass;
$baseClass->hello(); //Hello!

$derivedClass = new DerivedClass;
$derivedClass->hello('Moro!'); //Moro!
$derivedClass->hello(); //Warning: Missing argument 1 for DerivedClass::hello()

$randomClass = new RandomClass(new DerivedClass);
$randomClass->sayHello(); //Warning: Missing argument 1 for DerivedClass::hello()

The Alchemist [28.03.2013 12:27:19]

#

Jos php:ssä tapahtuu jotain epäloogista, niin syy on aina taaksepäin yhteensopivuuden säilyttäminen ja alkuperäisten suunnittelijoiden tyhmyys. Php on ennen versiota 5.3.3 ollut aika hirveä sekasotku, jota on vähän kerrassaan purettu auki. En tosin tarkoita, etteikö kieli olisi edelleen sekava...

Yksi ihmeellisyys on esimerkiksi konstruktoreita koskeva sääntö versiosta 5.3.3 alkaen:

class Foo {
    function Foo() { }
}

Funktio Foo::Foo tulkitaan luokan konstruktoriksi jos ja vain jos luokka ei ole namespacen sisällä. Muutoin sitä käsitellään tavallisena funktiona.

Mitä tulee virheilmoitusten tasoihin, niin mielestäni kaikki noticet pitäisi muuttaa fatal erroreiksi tai warningeiksi ja notice-taso poistaa käytöstä. Notice-virheet ovat yleensä sellaisia, joihin vain aloittelevat koodarit törmäävät, mutta jotka olisivat yleensä tappavia virheitä vähemmän lepsuissa kielissä.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta