referencia változó módosítása osztályban

aruna képe

function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'view':
      $obj = new myModuleNode($node);
      print $node->title; // print "Old title"
      $obj.changeNode();
      print $obj->node->title; // print "New title"
      print $node->title; // print "New title" as well
      break;
  }
}
 
public class mymoduleNode() {
  public $node;
  function __construct($node) {
    $this->node = $node;
  }
 
  public changeNode() {
    // Change node
    $this->node->title = "New title";
  }
}

Összefoglalva: A class-ban ugyanazt az eredeti node-ot akarom változtatni, amit a hook függvényben is tettem.

Az lenne a kérdésem, hogy csináljam? Mi erre a szép megoldás?

Köszönöm

Drupal verzió: 
Bálint képe

Ennek így működnie kell, az egyetlen furcsaság, hogy . operátorral próbálsz hozzáférni az osztályod metódusához. Szóval azt cseréld le, $obj.changeNode(); helyett $obj->changeNode(); legyen. Emiatt amúgy Fatal errort is kaptál, nem?

Egyébként a kérdésed kapcsán elolvastam ChX remek bejegyzését a témában, nekem eléggé felnyitotta a szemem, hátha hasznos lesz neked/nektek is.

(Ja, és gondolom a Drupal verzió 6.x akart lenni, 7.x-nél már nincs hook_nodeapi(). :))

0
0
aruna képe

d6 (elírtam)

Java kódban használtam objektumokat az utóbbi fél évben onnét jön a többi beidegződés. "Fatal errort is kaptál, nem?" - a fórum interpreter még csak egy warning-ot sem dobott. :)

0
0
Sk8erPeter képe

én csak visszakérdeznék:
igazából itt mi értelme van a mymoduleNode osztálynak? Mert annyi alapján, amit ez csinál, nem sok. Az egész igazából csak "zaj" a kódban, felesleges többlet.
lehetne simán a hook_nodeapi-ban
$node->title = "új cím";
nem kell erre külön osztály, külön metódus, stb.

(Szerk.:) Az osztályban vétett hibákra Rimelek elég jól válaszolt.

Bálint:
szerintem a referencia megértéséhez a PHP manualja bőven elég lehet (lényegében a belinkelt tutorial azt mondja el másként, ami itt van):
http://php.net/manual/en/language.references.pass.php
:)

1
0
aruna képe

tényleg nem kell osztály, ez így van. :)

Ez a referencia dolog volt a kérdés.

0
0
Rimelek képe

Nem csak az $obj.changeNode() a furcsa, hanem az osztály deklarálása is. Mert az osztály fejlécében nem kell a "kerek" zárójelet kitenni. Valamint az osztálynak PHP-ben a Java-val szemben nincsen láthatóság jelzője. Tehát csak class MyModuleNode {

A changeNode elé pedig kell a function kulcsszó, nem úgy, mint java-ban.

De ezeket a nyilvánvalóan véletlen elírásokat leszámítva a kód jó úgy, ahogy van, ha a PHP 5 -öt használsz. Az objektumok ugyanis PHP 5 óta mindig referencia szerint adódnak át paraméterként is és értékadásnál sem készül róluk másolat.

Ha a $node változó nem objektum lenne, vagy a PHP 4-es verzióját használnád, akkor a megoldás a következő lenne:

Ahogyan a nodeapi hook is & jellel kapja meg a $node változót, úgy az osztály konstruktorának is úgy kellene azt várnia.

public function __construct(&$node) {

Ez persze csak arra volna elég, hogy a konstruktorban ugyanazt a változót érd el, mint amit átadtál a konstruktornak. De ha továbbadnád tulajdonságnak, akkor ott is kellene használni a referenciajelölést.

  1. function __construct(&$node) {
  2. $this->node = &$node;
  3. }

Viszont mivel az objektumokat az ember többnyire nem másolgatni akarja, így azoknál nem szükséges a referenciajelölés. Ha mégis valami oknál fogva másolatot akarsz az objektumról, akkor a clone kulcsszót kell használnod.

  1. function __construct($node) {
  2. $this->node = clone $node;
  3. }

Így már egy másolaton dolgoznál. És pontosan ugyanazt az objektumot kapnád, mint az eredeti, csak más címen. Kivéve, ha változtatsz az adott osztály klónozásának működésén, de ebbe már nem megyek bele.

Tehát:
Ha egyszerre kellene neked az eredeti $node objektum és közben egy új is, akkor használnám a clone kulcsszót és másolnék. De nem ezt szeretnéd.

Ha kizárólag az eredeti objektum módosítására kell az osztály, és az osztály konkrétan a node-hoz tartozik, tehát a modul egyéb funkcióiban nem játszik szerepet, akkor nem is használnék osztályt. Vagy ha több ilyen változtatás van, akkor statikus metódusokat használnák.

  1. class MyModuleNode {
  2. public static function changeNode1($node) { //stb... }
  3. public static function changeNode2($node) { //stb... }
  4. ///stb...
  5. }

Ha pedig az eredeti, a drupal által stdClass-ba csomagolt node kiterjesztésére kell, és szintén más modulbeli műveleteket nem szolgál, akkor ilyesmit csinálnék:

  1. class mymoduleNode {
  2. private $node;
  3. function __construct($node) {
  4. $this->node = $node;
  5. }
  6.  
  7. public function changeNode() {
  8. // Change node
  9. $this->node->title = "New title";
  10. }
  11.  
  12. public function __get($var) {
  13. return $this->node->$var;
  14. }
  15.  
  16. public function __set($var, $value) {
  17. $this->node->$var = $value;
  18. }
  19.  
  20. public function __isset($var) {
  21. return isset($this->node->$var);
  22. }
  23.  
  24. public function __unset($var) {
  25. unset($this->node->$var);
  26. }
  27. }

A __get, __set, __isset, __unset metódusok miatt az értékadó és lekérdező műveletek, illetve az isset és unset műveletek is normál módon működnének. A nodeapi-ban pedig lecserélném a $node változót a sajátomra így:

$node = new myModuleNode($node);

És a drupal is úgy tud vele dolgozni, mint eredetileg, mert az eredeti node tulajdonságait éred el a kiterjesztetten keresztül. Viszont bővítheted saját metódusokkal, amiket felhasználhatsz. Ha kell, akkor vizsgálva, hogy milyen típusú a node.

  1. if ($node instanceof MyModuleNode) {
  2. //Hívhatod a saját metódusaid.
  3. } else {
  4. //valószínűleg normál stdClass típusú node.
  5. }

Remélem, nem voltam túl bőbeszédű és nem néztem el valamit.

7
0
Sk8erPeter képe

+1-eztem a hsz.-t azért, mert PHP-lecke gyanánt szerintem tök jól jöhet PHP-ben kezdő OOP-seknek, DE azt azért tegyük hozzá - szerintem -, hogy csak NAGYON indokolt esetben érdemes ilyen osztályt írni, ha valaki tényleg tudja, mit csinál, és sok egyedi metódusra van szüksége, mert különben csak a kódbázis méretéhez, a futási időhöz és a memóriaigényhez tesz hozzá (még ha minimálisan is), jelentős valódi előnyök nélkül.

0
0
Rimelek képe

Igen. Ezzel teljesen egyetértek.

0
0
aruna képe

a választ. Ezt tényleg elég bőséges. Chx cikkét is megnézem majd.

0
0
Bálint képe

"Az objektumok ugyanis PHP 5 óta mindig referencia szerint adódnak át paraméterként is és értékadásnál sem készül róluk másolat."

A mai napig ezt én is így fogalmaztam volna meg. ChX cikke azonban rávilágít, hogy ez nem így van, érdemes elolvasni.

1
0
chx képe

Nem keverjük a handler és a resource paraméterátadást mert egy nap irtó pofáraesés lesz belőle. Így lehet a legremekebb, legnehezebben lenyomozható bugokat előállítani :D ha referenciával veszel át egy objektumot és bizonyos esetben a változónak más értéket adsz.

function x($object) {
  $object = 4;
}
function y(&$object) {
  $object = 4;
}

ezek nem ugyanazt csináják.

function x($object) {
  $object->foo = 4;
}
function y(&$object) {
  $object->foo = 4;
}

Ezek igen, viszont ástál magadnak egy szép kis vermet. Ne tegyünk tehát és jelet object változók elé.
3
0
chx képe

Kevésbé kockáknak: képzelj egy szép kis fiókos szekrényt, a fiókon a változó neve a címke, a fiókban meg a változó tartalma van. Egy PHP referencia pusztán egy újabb címke a fiókon. Az unset törli a címkét a fiókról, amelyik fióknak nincs több címkéje, az elérhetetlen, ezért a PHP szépen ki is dobja a tartalmát. A resource és object típusú változók a fiókban csak egy speciális bejegyzést tartalmaznak amin az áll "az én értékemet egy másik szekrényben (megfelelő resource vagy class) találod az X. számú fiókban". Ezért amikor átadjuk az ilyen változókat egy függvénynek akkor csupán lemásoljuk ezt a bejegyzést, nem csoda tehát hogy a függvényben a speciális szekrényben található értéket manipulálják. Ha referenciaként adjuk át akkor viszont lehetőség nyílik a fő szekrényben a bejegyzés kidobására és új bejegyzés készítésére.

Kockáknak: http://php.net/manual/en/internals2.variables.intro.php a PHP változók két részből állnak: egyrészt a változó neve ami szokás szerint a szimbólumtáblába kerül plusz egy mutató a változó tartalmára (ami egy zval struktúra). A $a = &$b után a és b ugyanarra a zval-ra mutat. A zval-ban van egy számláló (refcount) hogy hányan mutatnak rá, amikor ez nulla akkor eldobjuk. A tényleges változó érték egy zvalue_value union-ban van amire a zval-ban van egy pointer.

5
0
Rimelek képe

Igaz, igaz. A működést valójában ismerem, csak nem gondoltam bele a hibás megfogalmazásomba.

Ez a fiókos szekrény egy igen kedvelt szemléltető példa. Úgy látszik, ehhez már túl kocka vagyok, mert olvasás közben elvesztettem a fonalat. A kockáknak valóhoz meg nem vagyok elég kocka. Viszont a php.net-en az "Objects and references" pontban ez az első két bekezdés is. Szerintem elég lényegretörően megfogalmazza.

1
0