[PHP] OOP reference naar (grand?)parent in subclass

Pagina: 1
Acties:
  • 174 views sinds 30-01-2008
  • Reageer

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik ben bezig met een webshop te programmeren in OOP, en ik heb de volgende structuur:

code:
1
2
3
4
Shop
    Category
        Subcategory
            Product


Ik denk dat ik het principe van extends niet helemaal snap. Volgens mij kan ik de verschillende niveaus niet 'extenden' aan elkaar omdat ze niet dezelfde basiseigenschappen hebben.
Hoe kan ik in PHP een diepte aangeven op de manier hierboven?

Ik heb nu (samengevat) het volgende:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
class Shop {
    var $categories;

    function Shop($categories = array())
    {
        $this->categories = $categories;
    }
}


class ShopElement
{
    var $id;
    var $title;

    function ShopElement($id, $title = "")
    {
        $this->set_id($id);
        $this->set_title($title);
    }

    //
    // General functions
    //
    function get_id()          { return $this->id;      }
    function set_id($id)       { $this->id    = $id;    }
    function get_title()       { return $this->title;   }
    function set_title($title) { $this->title = $title; }
}


class ShopCategory extends ShopElement
{
    var $id;
    var $title;
    var $subcategories;

    var $filename;
    var $icon_filename;
    var $description;

    function ShopCategory($id, $title = "", $subcategories = array(),
                          $filename = "", $icon_filename = "", $description = "")
    {
        parent::ShopElement($id);
        $this->title          = $title;
        $this->subcategories  = $subcategories;

        $this->filename       = $filename;
        $this->icon_filename  = $icon_filename;
        $this->description    = $description;
    }
}


class ShopSubcategory extends ShopElement
{
    var $id;
    var $title;
    var $products;

    var $description;
    var $img_filename;
    var $thumbnails;

    function ShopSubcategory($id, $title = "", $products = array(),
                             $description = "", $img_filename = "", $thumbnails = array())
    {
        parent::ShopElement($id);
        $this->title        = $title;
        $this->products     = $products;

        $this->description  = $description;
        $this->img_filename = $img_filename;
        $this->thumbnails   = $thumbnails;
    }
}


class ShopProduct extends ShopElement
{
    var $id;
    var $title;
    var $price;
    var $description;
    var $quantity;
    var $status;
    var $img_filename;
    var $rel_combo_id;

    function ShopProduct($id, $title = "", $price = false, $description = "",
                         $quantity = false, $status = false, $img_filename = "",
                         $rel_combo_id = false)
    {
        parent::ShopElement($id);
        $this->title        = $title;
        $this->price        = $price;
        $this->description  = $description;
        $this->quantity     = $quantity;
        $this->status       = $status;
        $this->img_filename = $img_filename;
        $this->rel_combo_id = $rel_combo_id;
    }
}

De website is opgebouwd uit frames en ik wil het aantal queries een beetje beperken. Ik wil dus zo veel mogelijk gegevens in het geheugen zetten zodat er niet opnieuw naar de database hoeft 'gequeryt' te worden.
Als ik bijvoorbeeld een pagina opvraag en alleen de id van het product weet (onderste niveau), wil ik dus een Shop object maken die alleen de categorie en subcategorie bevat van het opgevraagde product.
Is dit de goeie keuze? Hoe kan ik dat het beste uitwerken?

Ik weet dat je met JavaScript met sommige objecten referenties hebt naar de parents of grandparents, bijv. met form elements een referentie object naar het form element.
Kan zoiets ook met PHP? Is het wel handig om daarmee te werken?

[ Voor 20% gewijzigd door Verwijderd op 15-07-2004 23:03 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Hoi RSZ!

Uitgebreide reply volgt... :p

---

Ik denk dat je een beetje in de war bent over het relatietype. Dat wil zeggen, je geeft wel een mooie structuur aan, maar je zegt er niet bij wat de structuur moet voorstellen.

Ik denk dat je hier het beste onderscheid kunt maken tussen twee relaties van klassen. De ene is `isEen' (ook wel bekend als `isA', op z'n Engels), de andere is `bevat'.

`isEen' is een term die de lading niet helemaal dekt, en waarschijnlijk is dit ook niet helemaal de juiste naam ;). In elk geval gebruik je die relatie om een specialisering, of subclassering aan te geven. Je hebt twee objecten, en het eerste is net zoals het tweede, maar dan specifieker. Een typisch voorbeeld hier is natuurlijk de klasse Mens, en je kunt daarbij een subklasse Man hebben. Je kunt dan zeggen `Man isEen Mens'. Deze relatie programmeer je met extends. extends zorgt er daarbij voor dat Man alle eigenschappen van Mens overerft, en daar nog dingen aan toe kan voegen.

Dit is echter waarschijnlijk niet de relatie die je bedoelt. Immers, een Category isGeen (;)) Shop, en een Product isGeen Subcategory. Ik denk dat je de `bevat' relatie wilt modelleren. Dat wil zeggen, een Shop bevat Categories, een Category bevat Subcategories, en een Subcategory bevat Producten.

Dit kan, uiteraard, maar er is geen ingebouwde taalconstructie om dit aan te geven. Het beste wat je kan doen is een klasse Shop maken, die een verzamelign van Categories bevat, en dat is weer een klasse die Subcategories bevat, etc. Hoe maak je zo'n verzameling? Nou, in PHP wordt zo'n beetje alles gedaan met Arrays. Dus ik zou zeggen, met een Array ;).

---

Tot zover over inheritance en aggregatie (mooi woord) relaties.

Je zegt dat de website is opgebouwd uit frames. Nou is dat leuk (ieeeekk!), maar dat beïnvloedt denk ik niet het aantal queries. Elke pagina moet toch op zich geladen worden. TENZIJ je van plan bent om alle klassen in een sessie te gaan bewaren, en op die manier queries naar de database te besparen.

Mmm... ik weet niet. Ik heb er geen praktische ervaring mee, en ik zou er zo niet de vinger op een zere plek kunnen leggen, maar het idee staat me een beetje tegen. Queriën moet je toch, en je kunt jezelf waarschijnlijk veel bugs en gotcha's besparen als je gewoon 1 (of een paar) queries per pagina uitvoert. Bovendien is een database er tenslotte toch op gemaakt om gequeriet te worden, dus zo'n vaart zal het met de performance niet lopen :).

---

Hoe kun je een complete hiërarchische structuur aan objecten opbouwen gegeven alleen het id van het onderste element (product). Nou, niet in Product zelf, en ik zou het ook niet doen in een van de andere klassen. Dit is een typisch voorbeeld waar een Factory goed werk kan bewijzen (Ik zou zeggen, Google ook eens op het Factory pattern, of kijk op http://www.phppatterns.com/).

Ik stel voor dat je een ShopFactory klasse aanmaakt (een functie kan ook, maar klasses zijn netter, dus wen er maar alvast aan ;)), die je op elke pagina vraagt om een Shop object (Voor de duidelijkheid, een object is de naam voor een instantie van een klasse. Niet teveel op letten als je 't niet meteen doorhebt, is niet zo relevant). Wat deze Factory nu zal doen is de invoer controleren op product id's, category id's etc, en het Shop object meteen met de relevante informatie vullen. Als je dat hebt, kun je in je hoofdcode altijd gewoon de ShopFactory een Shop laten uitpoepen, en weet je meteen zeker dat alle relevante informatie erin staat.

Je gaat hier alleen tegen problemen aanlopen, als bijvoorbeeld een (sub)category id gespecificeerd is. Ga je dan meteen alle producten in die categorie uit de database queriën, zodat je ze daarna makkelijk weer kan geven? Maar als je nou met pagina's werkt? Alle producten queriën is dan teveel van het goeie en slecht voor de performance. Maar om wel fatsoenlijk te kunnen queriën op pagina's, moet de ShopFactory kennis hebben van pagina's. Slecht idee, want als je zo bezig bent begin je alle logica in de Factory te proppen en daar is-'ie niet voor bedoeld.

Je hebt een paar keuzes om dat op te lossen, die ik zo uit mijn hoofd kan bedenken. Waarschijnlijk zijn er nog (veel?) meer, maar hier zijn er alvast een paar :-
  • De makkelijkste en duidelijkste: niet automatisch producten laten ophalen bij de initialisatie. Voor categoriën zou je dat bijvoorbeeld wel kunnen doen, omdat die er niet zoveel zullen zijn, maar producten wordt te erg. Zodra je producten wilt gaan opzoeken, roep je bijvoorbeeld een methode aan: $category->retrieveProducts($page). Daar wordt de productenlijst gevuld en daarna kun je er doorheen gaan wandelen. Niet super-elegant, maar adequaat.
  • Het ophalen doen in een aparte initialisatiemethode, bijvoorbeeld init(), die aangeroepen wordt door de Factory. Deze methode haalt het gewenste paginanummer uit de invoer van de pagina ($_REQUEST), en haalt de daarvoor juiste producten op uit de database. Voordeel: de code die categorie gebruikt hoeft er niks speciaals voor te doen. Van de andere kant, je kan dit net zo goed doen in de constructor. En dat brengt ons op:
  • Het ophalen doen in de constructor. Werkt hetzelfde als in de vorige methode, maar is netter omdat je niet nog eens een expliciete aanroep naar een initialisatiemethode hoeft uit te voeren. Deze methode zou mijn voorkeur hebben.
Ik merk nu alleen dat de initialisatie nogal context-afhankelijk is. Je kan in een categorie namelijk wel alle (of een deel van de) producten op gaan halen, maar als de gebruiker nu een product aan het bekijken is, zijn die allemaal voor niks. Je moet dus flinke case-analysis doen om het juiste gedrag te voorschijn te toveren. Daar wordt je code onnodig ingewikkeld en weinig robuust van. En daarom...

(P.S: Case analysis is ook weer een heel erg mooie term die niks anders betkent dan `met een hele hoop IFjes kijken in wat voor situatie we ons precies bevinden'. Dit wil je dus zoveel mogelijk vermijden, daarom hebben we ook object-oriëntatie.)

---

...schoot mij net een volledig ander ontwerp te binnen. In principe ligt de focus van de gebruiker altijd op één van de elementen die je aangeeft in je lijstje, te weten Shop, Category, Subcategory of Product. De gebruiker is altijd één van die dingen aan het bekijken (minus uitzonderingspagina's zoals het winkelmandje of zo, die laten we even voor wat ze zijn). Van de bovenliggende objecten hoeven we geen details te weten, van de onderste wel. Het probleem is alleen, dat als we de datastructuur van boven af opbouwen (en ook beginnen bij het bovenste object), dat we dan elke keer moeten gaan kijken hoe diep we de structuur in kunnen duiken om te weten waar we zijn. Dat is vervelend, en bovendien onnodig.

In plaats van deze top-down strategie, denk ik dat een bottom-up strategie veel meer van toepassing is. Dat wil zeggen, we beginnen bij het laagste element uit de lijst, kijken wat voor type het is en bouwen aan de hand daarvan een pagina op. Eventuele hoger liggende niveaus worden wel ingeladen, maar de details niet. Waarschijnlijk heb je ze toch alleen nodig om een "escape"-link te laten zien, of de structuur.

We introduceren even de term "focus", om aan te geven dat een gebruiker op het moment geïnteresserd is in een object. Dus een category kan focus hebben, of een product. Als de gebruiker net de site binnenkomt zeggen we dat de Shop focus heeft.

Terug naar de Factory. De Factory analyseert wat voor object er op het moment focus heeft (van alle id's die beschikbaar zijn pakt hij het id dat hoort bij het object dat het diepst in de lijst zit, dus eerst Product, dan Subcategory, Dan Category en dan Shop), en maakt daar een instantie van aan.

Elk object heeft wel een link naar zijn ouder. Merk op dat dit net andersom is dan wat ik net zei, namelijk dat een ouder een verzameling kinderen heeft. De andere kinderen zijn niet relevant, we willen alleen de ouder van het kind met focus weten.

In principe kan elk object in zijn constructor zelf zijn ouder opzoeken en aanmaken, en de link in zichzelf opslaan. Wat we dan alleen nog nodig hebben, zijn een methode om details over het object op te vragen (die wordt alleen aangeroepen op het onderste object, dus het object met focus), en het is natuurlijk helemaal mooi om elk object zijn eigen pagina te laten weergeven. Nog minder case analysis!

Een kort stukje code om te laten zien hoe elegant de code dan kan worden:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ItemFactory {
  function ItemFactory() {
  }

  function getFocused($input) {
    // Kijk welk item gefocused is (waar de id van beschikbaar is)
    // en maak die aan.
    if (isset($input["pid"])) $ret =& new Product($input["pid"]));
    elseif (isset($input["uid"])) $ret =& new SubCategory($input["uid"]));
    elseif (isset($input["cid"])) $ret =& new Category($input["cid"]));
    elseif (isset($input["sid"])) $ret =& new Shop($input["sid"]));
    else $ret =& new Shop(DEFAULT_SHOP);

    $ret->setFocus();
    return $ret;
  }
}

// Dit is de hoofdcode -- haal het gefocuste object op een toon het
$fact =& new ItemFactory();
$focused = $fact->getFocused($_REQUEST);

// Het object weet zelf hoe het zichzelf moet afbeelden op het scherm
// Dat laten we 'm nu doen
$focused->display();


Dit is natuurlijk een puur programmatisch verkooppraatje ;). KIJK nou toch eens hoe mooi die code kan worden! Uiteraard is de code nog lang niet compleet. De echte nitty-gritty details over de objecten zelf zijn weggelaten. Om te beginnen moeten ze allemaal afgeleid zijn van één basisobject, wat de eigenschappen in zich heeft om "gefocused" te worden (met setFocus()), en om zichzelf af te beelden (met display()). Elk object moet op zijn beurt weten wat er van verwacht wordt als het focus heeft, èn het moet zijn ouder opzoeken in de database. Hier gebruik je dus wèl de extends relatie, omdat de subobjecten specialisaties zijn van het hoodfobject. Bovendien speelt de andere relatie ook nog een rol (zij het in mindere zin), want de verschillende klassen kunnen onderling nog verwijzingen naar elkaar hebben.

Mocht je daar nog voorbeeldcode van willen zien, laat het dan ff weten, dan zal ik die hier ook nog neerplakken. Maar ik denk, geen reden om mijn vingers extra moe te maken, eerst zien of 't aanslaat ;).

---

En bij nader inzien is dit waarschijnlijk ook wat jij in gedachten had toen je de post schreef. Ik moet toch eens leren beter te lezen ;). En om je vraag over referenties naar ouders te beantwoorden: je kan doen wat je zelf maakt. Dus als je referenties naar ouders maakt, dan heb je die. Dit heeft echter alleen betekenis in een `bevat' relatie (of een ander soort). In een `isEen', of subclassing, relatie bestaat een referentie naar je vader niet, want je bent daar zelf hetzelfde object als je vader. Jij hebt alleen wat meer eigenschappen.

Groeten,
One

[ Voor 67% gewijzigd door Verwijderd op 16-07-2004 10:54 ]


Acties:
  • 0 Henk 'm!

  • dingstje
  • Registratie: Augustus 2002
  • Laatst online: 02-01-2024
OneOfBorg: ik ben dan wel niet de TS, maar toch hartelijk bedankt. Ik heb hier erg veel van geleerd over OOP.

If you can't beat them, try harder


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Waarom een factory pattern? Die is volgens mij bedoelt om classes met de zelfde interface te returnen, wat een Shop en een Product zeker niet hebben, bijvoorbeeld een getPrice method waar Shop niets aan heeft.

Acties:
  • 0 Henk 'm!

Verwijderd

PrisonerOfPain schreef op 16 juli 2004 @ 11:41:
Waarom een factory pattern? Die is volgens mij bedoelt om classes met de zelfde interface te returnen, wat een Shop en een Product zeker niet hebben, bijvoorbeeld een getPrice method waar Shop niets aan heeft.
Op zich is dat wel vaak zo ja, hoewel het natuurlijk niet persé hoeft. In statisch getypeerde talen heb je alleen vaak geen keus. PHP is echter niet statisch getypeerd; er is geen reden om je te beperken tot objecten met dezelfde interface.


Echter, ik denk dat ik je opmerking niet helemaal snap, want in allebei de Factory patterns wordt er wel degelijk hetzelfde (type) object teruggegeven. In de eerste is het elke keer een Shop (geen probleem lijkt me).

In de tweede is het elke keer een object van het type ShopElement, om ReSistanZ' benaming maar aan te houden. De superklasse waar alle "focusbare objecten" van afstammen. Dat het de ene keer om een andere subklasse gaat dan de andere keer is niet relevant, vanwege het principe van substitutability.

En in statisch getypeerde talen implementeer je het precies hetzelfde.

Kijk, je hebt wel gelijk dat een Shop en een Product niet dezelfde interface hebben, maar voor het stuk code dat de Factory gebruikt is dat niet relevant. Deze code verwacht alleen een object dat gefocused kan worden, en afgebeeld, en dat kunnen alle klassen omdat ze dat hebben geërfd van ShopElement. Zodra een stuk code specifiek een Product of een Category verwacht wordt het een ander verhaal, maar dat is hier niet aan de orde.

Dat wordt het wel in de klasse-definities zelf. Waar het geen probleem vormt, omdat je in een klasse toch wel weet met wat voor type object je te maken hebt (namelijk van het type dat je zelf bent). En als een Product klasse een referentie heeft naar een Subcategory klasse, dan heeft hij volgens dit model die referentie zelf aangemaakt/ergens vandaan getoverd in zijn constructor. Hij weet dus daar zelf om wat voor type object het gaat, en weet dus de interface van het object precies. Maar dit heeft dus verder niets (meer) te maken met de Factory.

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
One, ten eerste: bedankt voor je uitgebreide reactie! :)
Het verduidelijkt de theorie een hoop zo, dat is mooi.

Tegelijkertijd ben ik erachter gekomen waar mijn gebrek aan kennis ligt hier, en volgens mij is dat bij het gebruik van references met de reference-operator &.
Ik heb al een paar tutorials gelezen over het gebruik ervan, maar ik snap dus niet waarom je de syntax gebruikt in een class constructor bijvoorbeeld.

In de code die je schreef:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ItemFactory { 
  function ItemFactory() { 
  } 

  function getFocused($input) { 
    // Kijk welk item gefocused is (waar de id van beschikbaar is) 
    // en maak die aan. 
    if (isset($input["pid"])) $ret =& new Product($input["pid"])); 
    elseif (isset($input["uid"])) $ret =& new SubCategory($input["uid"])); 
    elseif (isset($input["cid"])) $ret =& new Category($input["cid"])); 
    elseif (isset($input["sid"])) $ret =& new Shop($input["sid"])); 
    else $ret =& new Shop(DEFAULT_SHOP); 

    $ret->setFocus(); 
    return $ret; 
  } 
}

$fact =& new ItemFactory();
$focused = $fact->getFocused($_REQUEST);


Waarom die =& in die functie en bij het instantiëren van die ItemFactory? En hoe zou ik in mijn situatie met de Shop dan referenties maken naar de parents van ShopProduct en ShopSubcategory?

Nogmaals bedankt voor de geweldige hoeveelheid typwerk en tijd! :)

Acties:
  • 0 Henk 'm!

Verwijderd

die =& is ervoor om niet een copie van je object die je maakt aan $fact toe te wijzen maar het object wat gemaakt wordt.

ff wat duidelijker, in PHP (< versie 5) wordt er standaard een copie gemaakt als je
PHP:
1
$mijnobject = $object;


doet, d.w.z na het uitvoeren van deze code heb je 2 objecten, identiek weliswaar maar wel apart.

PHP:
1
$mijnobject =& $object;


zorgt ervoor dat $mijnobject en $object naar hetzelfde object wijzen

de eerte variant heet assignment by value (waarde wordt gekopieerd)
de tweede heet assignment by reference (pointer naar de waarde wordt gekopieerd)

als je na het eerste stukje code $mijnobject veranderd blijft $object zoals ie was, en er ontstaat dus een verschil tussen de twee

als je na het 2de stukje code $mijnobject veranderdt. blijft $object identiek aan $mijnobject.


references zijn eigenlijk niks anders dan wat pointers in C of C++ zijn.
het verschil is dat je in php niet hoeft te weten of je een referentie of een waarde gebruikt, php vogelt 't allemaal voor je uit en "Does The Right Thing" (tm)


ff nog vergeten, het grote voordeel van =& is dus dat je bespaart op je geheugen gebruik. en objecten doen meer wat je (ik i.i.g. ;)) zou verwachten.
PHP5 doet het helemaal anders (veel beter naar mijn mening) met objecten, zie hiervoor deze link

[ Voor 18% gewijzigd door Verwijderd op 16-07-2004 16:38 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Verwijderd schreef op 16 juli 2004 @ 16:23:

references zijn eigenlijk niks anders dan wat pointers in C of C++ zijn.
het verschil is dat je in php niet hoeft te weten of je een referentie of een waarde gebruikt, php vogelt 't allemaal voor je uit en "Does The Right Thing" (tm)
They are not like C pointers, they are symbol table aliases. Note that in PHP, variable name and variable content are different, so the same content can have different names.
Variabelen gelijk aan elkaar maken door =& snap ik wel. Ik snap alleen niet waarom je die & bij functies of class methods zou willen gebruiken.

PHP:
1
2
3
4
5
6
7
8
9
function &bar()
{
   $a = 5;
   return $a;
}
foo(bar());

// of bijv:
$fact =& new ItemFactory();

Waarom assign by reference terwijl je de functie of class niet kan veranderen zoals bijv. een variabele?

[ Voor 3% gewijzigd door Verwijderd op 16-07-2004 16:47 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Aan 't verhaal van ReSc heb ik helemaal niks toe te voegen. Zo dat is mooi, scheelt me weer vingertoppen deze keer. Toch maar eens Dvorak leren ;)
Verwijderd schreef op 16 juli 2004 @ 15:19:
En hoe zou ik in mijn situatie met de Shop dan referenties maken naar de parents van ShopProduct en ShopSubcategory?
Ja, dat is het mystieke deeltje weggelaten code ;). Het makkelijkst zou het zijn om in de constructor van elk object (dwz, de Product, Subcategory en Category objecten), op te zoeken wat het "bevattende" object is, en dat dan aan te maken. En natuurlijk de referentie op te slaan. Het object wat je net aangemaakt hebt (dat van één laagje hoger), doet op zijn beurt in zijn constructor hetzelfde, en zo ga je door tot je aan de Shop zit.

Voorbeeldje :

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* 
  We nemen aan dat we hier een superklasse 
  ShopElement hebben die de gewenste focusbaarheid 
  eigenschappen heeft etc.

  Bovendien ga ik ervan uit dat er ergens een $db object
  rondzwerft waar een database connectie in zit, en dat
  die ongeveer zo werkt als die uit vBB. (Ieks! Globale variabelen ;)
*/

class Product extends ShopElement {
  var $id, $name, $price, ...;  

  function Product($id) {
    // First, get some global information about the current product.
    $this->id = $id;
    $this->getInfoFromDbase();

    // Then get the containing subcategory, and instantiate it
    $this->subcategory =& new Subcategory($this->subcatid);          
  }
   
  function getInfoFromDbase() {
    global $db;
    $info = $db->queryFirst("SELECT name, subcatid, ... FROM product WHERE id = $this->id");
    
    $this->name = $info["name"];
    $this->subcatid = $info["subcatid"];
    // ... meer ?
  }

  function setFocus() {
    // Als we focus hebben (kan eigenlijk bijna niet anders als dit ding
    // überhaupt geladen wordt, maar vooruit) laden we de rest van de 
    // attributen ook in.
    global $db;    
    $info = $db->queryFirst("SELECT * FROM product WHERE id = $this->id");
   
    $this->price = $info["price"];
    // ... meer ...

    // Zoals je ziet doen we bijna dezelfde query vaker. Dit kan misschien
    // beter geoptimaliseerd worden...
  }

  function display() {
    echo "Ik ben $this->name en ik zit in subcategorie " . $this->subcategory->makeLink() . ". Bovendien kost ik $this->price.";
  }
}

class Subcategory extends ShopElement {
  var $id, $name, $products;

  function Subcategory($id) {
    $this->products = Array();

    $this->id = $id;
    $this->getInfoFromDbase();

    $this->category =& new Category($this->catid);
  }

  /* getInfoFromDbase() geloof je wel denk ik */

  function setFocus() {
    // Hier gaan we alle producten opvragen in deze subcategorie
    // Tis maar een voorbeeldje
    global $db;
    
    $res = $db->query("SELECT * FROM product WHERE subcatid = $this->id");
    while ($row = $db->nextRow($res)) {
      $this->products[] = $row;
    }   
  }
 
  function makeLink() {
    return "<a href='?uid={$this->id}'>{$this->name}</a>";
  }

  function display() { 
    // Tabelletje uitpoepen van $this->products
    // ...
  }
}

/*
  En net zo voor Category en Shop.
*/


Deze code valt te runnen met het vorige voorbeeld van de factory (modulo wat weglatingen en typ/codefouten die ik over het hoofd heb gezien ;)).

Ik bedenk me net trouwens weer iets... in principe wordt een object natuurlijk alleen afgebeeld als het focus heeft -- OF NIET. En als je voor de aanroep van display moet weten of de gegevens wel opgehaald zijn... sja... niet bepaald mooi he?

Het mooiste zou het zijn om geen setFocus te gebruiken, maar een acquireData ofzo (ik hou van het woord "acquire" ;)). Deze haalt de gegevens op uit de database als het object getoond moet worden (dus wordt aangeroepen in display()), maar doet dit niet als het al een keer gebeurt is. Dat kan met het bijhouden van een vlaggetje, en is voor het data/objectmodel waarschijnlijk nog iets netter.

Bovendien is het onderscheid tussen getInfoFromDbase en setFocus (die andere gegevens ophaalt) op de klasse Product nogal kunstmatig aangebracht. Eigenlijk moet je dat dus niet doen, want het is toch maar één rij, hoe je het wendt of keert.

Het onderscheid is er echter wèl bij objecten als Category, Subcategory en Shop (die zelf 1 record hebben ergens, maar ook nog een stel andere records kunnen bevatten). In dat geval haal je dus in getInfoFromDbase het ene record op, en in setFocus (of acquireData) de relationeel bijbehorende records.

[ Voor 12% gewijzigd door Verwijderd op 16-07-2004 17:10 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Verwijderd schreef op 16 juli 2004 @ 16:47:
[...]


[...]


Variabelen gelijk aan elkaar maken door =& snap ik wel. Ik snap alleen niet waarom je die & bij functies of class methods zou willen gebruiken.

PHP:
1
2
3
4
5
6
7
8
9
function &bar()
{
   $a = 5;
   return $a;
}
foo(bar());

// of bijv:
$fact =& new ItemFactory();

Waarom assign by reference terwijl je de functie of class niet kan veranderen zoals bijv. een variabele?
Daar heb je helemaal gelijk in!

Het gaat in deze gevallen dan ook niet om de klasse (niet te veranderen), maar om een instantie ervan. Die valt wel te veranderen, want die heeft bijvoorbeeld attributen (lees: variabelen).

Evenzo bij een functie/methode. Het gaat hier niet om de functie/methode zelf, maar om de teruggegeven waarde. Niet de mooiste syntax, maar whatever works.

Overigens heb je dat bijna nooit nodig. Het is voldoende om gewoon de syntax $foo =& new Foo() te gebruiken als je objecten maakt en er verder niet over na te denken.

Tegen de tijd dat PHP5 zwaar ingeburgerd is dan mag je dat weer laten gaan :D

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Verwijderd schreef op 16 juli 2004 @ 17:05:

Het gaat in deze gevallen dan ook niet om de klasse (niet te veranderen), maar om een instantie ervan. Die valt wel te veranderen, want die heeft bijvoorbeeld attributen (lees: variabelen).

Evenzo bij een functie/methode. Het gaat hier niet om de functie/methode zelf, maar om de teruggegeven waarde. Niet de mooiste syntax, maar whatever works.
Volgens mij zit er iets te hard in mijn hoofd gebeiteld... ;)
Als je een variabele definieert en nog één die een referentie is naar de eerste, zorgt ervoor dat je met de één de andere ook verandert.
Als je een variabele definieert die dus is(/als waarde heeft) wat de functie of klasse teruggeeft (met return), dan snap ik niet wat het gebruik van reference voor nut heeft als je de variabele sowieso al maakt(/als waarde geeft) wat die functie of klasse teruggeeft. (leuke zin!) ;)

Ik ga het hoofdstuk over references in de PHP manual nog even 2370927 x doorlezen!

Acties:
  • 0 Henk 'm!

Verwijderd

Over het algemeen heeft het geen zin, tenzij je statische variabelen gebruikt. Dat zijn variabelen die tussen aanroepen aan de functie bewaard blijven (dus niet weggegooid worden zodra de functie afgelopen is).

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik loop nu tegen 2 dingen aan:
  • Als ik de subcategorie id weet, dan wil ik 1 query doen voor alle producten onder die subcategorie
  • Hoe voorkom ik een oneindige boomstructuur aan product -> subcategory reference -> producten daaronder -> product -> subcategory reference -> producten daaronder -> product etc etc...
Ik begin de draad behoorlijk kwijt te raken nu... Ik word een beetje duizelig van al die references.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:27

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op 16 juli 2004 @ 16:23:
references zijn eigenlijk niks anders dan wat pointers in C of C++ zijn.
het verschil is dat je in php niet hoeft te weten of je een referentie of een waarde gebruikt, php vogelt 't allemaal voor je uit en "Does The Right Thing" (tm)
Hier wil ik toch even op inhaken, PHP's references zijn alles behalve C(++) pointers. In C++ wijst een pointer namelijk naar een stukje geheugen en een value-type niet. In PHP wijzen alle variabelen naar een stukje geheugen, $a = &$b zorgt er slechts voor dat $a naar hetzelfde stukje gaat wijzen als $b.

Ik heb het niet zo heel lang geleden eens helemaal uitgelegd: [rml].oisyn in "[ PHP] OO linked list maken"[/rml]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

Verwijderd

.oisyn schreef op 20 juli 2004 @ 17:34:
[...]


Hier wil ik toch even op inhaken, PHP's references zijn alles behalve C(++) pointers. In C++ wijst een pointer namelijk naar een stukje geheugen en een value-type niet. In PHP wijzen alle variabelen naar een stukje geheugen, $a = &$b zorgt er slechts voor dat $a naar hetzelfde stukje gaat wijzen als $b.

Ik heb het niet zo heel lang geleden eens helemaal uitgelegd: [rml].oisyn in "[ PHP] OO linked list maken"[/rml]
Jaah, dat is natuurlijk wel zo. En pointers zijn vies, en references niet. Maar laten we het even makkelijk houden.
Verwijderd schreef op 20 juli 2004 @ 17:22:
Ik loop nu tegen 2 dingen aan:
  • Als ik de subcategorie id weet, dan wil ik 1 query doen voor alle producten onder die subcategorie
  • Hoe voorkom ik een oneindige boomstructuur aan product -> subcategory reference -> producten daaronder -> product -> subcategory reference -> producten daaronder -> product etc etc...
Ik begin de draad behoorlijk kwijt te raken nu... Ik word een beetje duizelig van al die references.
Nou de eerste lijkt me tamelijk triviaal, afhankelijk van de opzet van je tabellen. Maar het volgende lijkt me toch wel logisch:

code:
1
SELECT * FROM product WHERE subcatid = $subcatid


En dat tweede: dat zou geen probleem moeten zijn, als je het doet zoals ik had voorgesteld. Je haalt dan de lijst van producten namelijk alleen op als de Subcategory zelf focus heeft. De producten die dan opgehaald worden hebben dus geen focus, dus daar hoeft in principe geen subcategory meer aan verbonden te worden. Als je dat toch doet zou het nòg niet zo'n probleem moeten zijn, omdat de aangemaakte subcategory zéker geen focus heeft, en dus niet nog eens alle producten ophaalt [Merk op dat de verschillende subcategory-objecten wel naar dezelfde subcategory verwijzen, maar niet hetzelfde object zijn].

Dit is misschien een beetje smerig; je zou daarom in de productklasse in plaats van een nieuwe instantie van een Subcategory aan te maken, het ook mogelijk kunnen maken al een referentie daarnaartoe op te geven. In dat geval haal je in de Subcategory klasse alle producten in die subcategory op, en geef je "de huidige instantie" mee aan elk product. Aangezien deze niet meer geïnitialiseerd hoeft te worden levert dat geen oneindige lus op, en het heeft het voordeel dat je datamodel lekker consistent is (Al is dat voor een per-request systeem als PHP waarschijnlijk niet eens nodig).

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:27

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op 20 juli 2004 @ 22:44:
Jaah, dat is natuurlijk wel zo.
Ik mag toch wel iets duidelijk maken :?
En pointers zijn vies, en references niet. Maar laten we het even makkelijk houden.
Als er een ding is wat vies is dan zijn dat php's references wel :Y)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

Verwijderd

.oisyn schreef op 20 juli 2004 @ 23:53:
Als er een ding is wat vies is dan zijn dat php's references wel :Y)
Valt toch wel mee? Behalve dan dat ze tamelijk ondoorzichtig zijn. Wat pas echt vies is, is het copy-on-assign mechanisme. Wat er in PHP5 dus uit gaat.

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Bedankt voor de replies!

Ik heb de parent-child-structuur zoals in de eerste post van dit topic. Nou vraag ik me af:
Is het handiger of beter om elk ShopElement attribuut $shop te geven dat een reference is naar de 'top' Shop instantie, of is het handiger om elk ShopElement een eigen attribuut te geven dat een reference is naar de parent? (dus ShopCategory een attribuut $shop, ShopSubcategory een attribuut $category, ShopProduct een attribuut $subcategory)

Ik heb nu de volgende structuur als ik een var_dump() doe op de Shop instantie, wanneer de pagina is aangevraagd met een subcategory id van "1":

http://www.rszdesign.com/test/test1.php

Nu vraag ik me af of dit wel zo efficiënt is, het ziet er namelijk niet zo efficiënt uit... ;) Ik kwam op de term circular referencing en ik zie dat PHP bij de var_dump() een paar keer detecteert dat er *RECURSION* optreedt...
  • Moet ik dit oplossen/veranderen?
  • Krijg je bij circular referencing altijd recursion?

[ Voor 5% gewijzigd door Verwijderd op 22-07-2004 17:01 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 00:27

.oisyn

Moderator Devschuur®

Demotivational Speaker

[quote]Verwijderd schreef op 22 juli 2004 @ 16:59:
Ik kwam op de term circular referencing en ik zie dat PHP bij de var_dump() een paar keer detecteert dat er *RECURSION* optreedt...
• Moet ik dit oplossen/veranderen?

Die recursion is logisch. Als je een Auto hebt met een referentie naar z'n 4 Wielen, en je hebt per Wiel een referentie naar de Auto waar ie bij hoort, dan heb je dus zo'n circular reference. Dit is echter gewoon logisch, er is niets mis mee.

Als je de Auto zou printen met var_dump, dan output var_dump dus alle eigenschappen van de Auto, inclusief z'n 4 wielen en hun eigenschappen. Maar een eigenschap van een Wiel is bij welke Auto hij hoort. Als var_dump dan opnieuw die auto moet gaan printen, moet ie ook weer opnieuw de wielen printen, en dan weer de auto, enz. Die RECURSION geeft dus aan dat er een object geoutput moet worden dat al een keer geoutput is, het betekent niet dat er een fout in je code zit :)
• Krijg je bij circular referencing altijd recursion?[/list]
Recursion is een programmeertechniek waarbij een stukje code zichzelf weer aanroept (al dan niet direct). Var_dump is zo'n recursieve functie, omdat het zichzelf steeds aanroept voor elke variabele in een object. Var_dump is er echter tegen beschermd, zoals je hebt kunnen zien. Als jij een dergelijke functie maakt moet je ervoor oppassen dat je niet ook in een oneindige recursie terecht komt, maar ik denk niet dat je daar snel last van zult hebben :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

Verwijderd

Nou de eerste lijkt me tamelijk triviaal, afhankelijk van de opzet van je tabellen. Maar het volgende lijkt me toch wel logisch:

code:
1
SELECT * FROM product WHERE subcatid = $subcatid
Dit snap ik niet helemaal, hieraan is toch niets oo's aan? Je selecteerd alle producten, maar wat moet je dan doen? (om het oo te houden)

Ik ben namelijk niet al te goed in oo, maar moet het wel een beetje leren.

Acties:
  • 0 Henk 'm!

Verwijderd

Verwijderd schreef op 22 juli 2004 @ 16:59:
Is het handiger of beter om elk ShopElement attribuut $shop te geven dat een reference is naar de 'top' Shop instantie, of is het handiger om elk ShopElement een eigen attribuut te geven dat een reference is naar de parent? (dus ShopCategory een attribuut $shop, ShopSubcategory een attribuut $category, ShopProduct een attribuut $subcategory)
"Handiger" is relatief... het hangt er maar net vanaf wat je er mee wilt doen. Maar ik zie geen probleem met elk element gewoon een verwijzing naar zijn (bevattende) ouder te laten hebben, en die verwijzingen te volgen tot boven in de boom. Desnoods geef je elk object een methode getShop(), die getShop() van zijn parent aanroept, en die weer van zijn parent, totdat je aan de Shop instantie bent gekomen die zichzelf terug geeft.

Maar ik ben kurieus: waarom wil je de "Shop" als klasse in je code opnemen?

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Verwijderd schreef op 22 juli 2004 @ 19:45:
[...]

Maar ik ben kurieus: waarom wil je de "Shop" als klasse in je code opnemen?
Shop is de interface toch? De shop bestaat uit menuitems, categorieën (->subcategorieën -> producten) en shopping cart.

Zo kan ik dus een $shop instantiëren aan het begin van de pagina als volgt (globaal/onuitgewerkt):
PHP:
1
2
3
4
5
6
7
$focus = false;

if (isset($_GET['cid'])) { $focus['cid'] = $_GET['cid']; }
if (isset($_GET['sid'])) { $focus['sid'] = $_GET['sid']; }
if (isset($_GET['pid'])) { $focus['pid'] = $_GET['pid']; }

$shop = new Shop($focus,$_SESSION['cart']);

En vanuit het object $shop kan ik dan alles uitvoeren. Leek me wel handig.

Acties:
  • 0 Henk 'm!

Verwijderd

Kan ook. Het een is niet beter of slechter dan het ander.

Doe maar wat je zelf het makkelijkst/fijnst lijkt :).

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik kom een aantal problemen tegen tijdens het implementeren van deze class in de website:
  • Het plaatsen van ShopProduct instanties in de shopping cart ($shop->cart->items) wordt een beetje overdreven op grootte van de sessie bestanden (eerst 1kb, nu 5kb). Hoe kan het beste de class gebruiken in combinatie met een shopping cart class?
  • Search engine -> de search zal meerdere producten kunnen vinden uit verschillende categorieën/subcategorieën, hoe kan ik de zoekresultaten dan het beste 'bottom-up' neerzetten in de class instanties?
Ik hoop dat ik het niet te vaag uitleg, zo ja komt dat waarschijnlijk omdat die hele structuur een beetje onduidelijk begint te worden (in mijn hoofd) door al die references die door elkaar heen gaan...

Acties:
  • 0 Henk 'm!

Verwijderd

Je zou kunnen controleren of er geen gegevens dubbel in staan; als dat wel zo is, dan moet er iets uitgedund worden aan je datastructuur.

Als alternatief: als je het niet erg vindt om elke keer als de pagina geladen wordt de gegevens opnieuw te querien, zou je __sleep() kunnen gebruiken om alleen de product id te serialiseren, en __wakeup() om na het inladen de gegevens opnieuw uit de database te halen (info).

En op je tweede vraag: als je de factory gebruikt, voer je 'm gewoon één voor één de product-ids van de gevonden items, en dan krijg je alle product classes gewoon terug, met alle bottom-up er al in.

Om nog wat geheugen te besparen (omdat meerdere producten bijvoorbeeld in dezelfde subcategory kunnen zitten), zou je voor elk niveau van de objecten een eigen factory kunnen maken; deze kunnen dan bijhouden welke objecten ze al hebben aangemaakt, en als daar een tweede verzoek voor komt een verwijzing naar de oude teruggeven (in plaats van een nieuwe maken). Op die manier bespaar je op geheugen (en misschien ook wel op ruimte in je sessiebestand).
Pagina: 1