magic __get __set

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Pagina: 1 2 3 4 volgende »

Ozzie PHP

Ozzie PHP

01/02/2013 01:45:42
Quote Anchor link
Ola,

Wie van jullie gebruikt wel eens de magic __get en __set methodes?
Ik denk erover om ze te gebruiken in m'n container, omdat dit...

echo $this->container->paths->private->library;

... me eigenlijk net wat mooier/handiger lijkt dan:

echo $this->container->get('paths')->private->library;

Op internet lees ik vaak dat je de magic __get en __set beter niet kunt gebruiken omdat ze vertragend zouden werken.

Ik heb zelf net een test gedaan met het verschil tussen een get() en __get() aanroep.
Bij een for loop van 1.000.000 echo's naar het scherm deed de get() method er ongeveer 1,8 seconde over en de __get() method ongeveer 1,95 seconde. Dat verschil valt op zich dus mee.

Nu is zo'n testje grappig, maar ik ben benieuwd of je er in de praktijk wel of niet iets van zal merken. Gebruikt iemand zelf hier de __get en __set methods, en zo ja... hoe bevalt dat?

In de betreffende container class heb ik maar 1 class property ingesteld, en verder dan alleen de __get en __set functie. Ik ben benieuwd naar jullie ervaringen.
 
PHP hulp

PHP hulp

27/11/2024 19:27:21
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 09:15:18
Quote Anchor link
Een belangrijk verschil tussen __get() en een eigen getter is dat __get() automatisch wordt aangeroepen bij een eigenschap die ontoegankelijk is. In dit voorbeeld is dat bijvoorbeeld private $Gebruikersnaam:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Gebruiker
{
    private $Gebruikersnaam;

    public function construct($gebruikersnaam)
    {

        $this->Gebruikersnaam = $gebruikersnaam;
    }
}


/* Nieuwe gebruiker met een gebruikersnaam */
$g = new Gebruiker('Admin');
/**
 * Deze aanroep van een ontoegankelijke eigenschap strandt in een
 * Fatal Error: Cannot access private property Gebruiker::$Gebruikersnaam.
 */

echo $g->Gebruikersnaam;
?>


Door de fatale fout stopt het script resoluut. Maar misschien wil je dat helemaal niet en kan het script ook in leven blijven bij een ongeldige aanroep. Je kunt __get() dan bijvoorbeeld zo aan het werk zettten:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Gebruiker
{
    private $Gebruikersnaam;

    public function construct($gebruikersnaam)
    {

        $this->Gebruikersnaam = $gebruikersnaam;
    }


    public function __get($name)
    {

        return null;
    }
}


/**
 * De gebruikersnaam is nog steeds een ontoegankelijke eigenschap,
 * maar een onschuldige fout wordt onderdrukt.
 */

$g = new Gebruiker('Admin');
echo $g->Gebruikersnaam;
?>


Een magische methode zoals __get() wordt automatisch aangeroepen. Dat maakt magische methoden dus belangrijk voor alle aanroepen waarvoor je niet zelf expliciet een getter hebt geschreven. Dat geldt dan weer per definitie voor alle eigenschappen die geen getter hebben.

Ik zou __get() alleen niet gebruiken (of: misbruiken) om eigenschappen die je eerst als private of protected declareert alsnog toegankelijk te maken. Dan kun je de eigenschap beter public maken.
 
Ozzie PHP

Ozzie PHP

01/02/2013 11:27:11
Quote Anchor link
Ward, dankjewel voor de toelichting... alleen niet helemaal het antwoord op mijn vraag :-)

Ik wil het voor niet-gedefinieerde properties (bijv. configuratie-instellingen) gebruiken.

Bijv. ik wil een basis-url instellen in mijn container.

Dan wil ik dit doen:

$container->base_url = 'www.mijnsite.nl';

en om op te vragen

echo $container->base_url;

Ik wil hier de magic __get en __set voor gebruiken. Maar de vraag is of dat in de praktijk MERKBAAR vertragend werkt?
 
Erwin H

Erwin H

01/02/2013 11:34:52
Quote Anchor link
Ozzie PHP op 01/02/2013 11:27:11:
Maar de vraag is of dat in de praktijk MERKBAAR vertragend werkt?

Benchmarken....

En als je er zorgen over hebt gebruik je toch gewoon een 'custom magic getter':
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
<?php
class MyClass{
  private $data = array();
 
  public function get($name){
    return (isset($this->data[$name]))?$this->data[$name]:'';
  }
}

?>
 
Ozzie PHP

Ozzie PHP

01/02/2013 11:37:02
Quote Anchor link
Erwin, lees mijn 1e post... ik twijfel dus tussen een normale get() en een magic __get.
Die laatste variant vind ik makkelijker werken (zie mijn 1e post), maar de vraag is of zoiets MERKBAAR vertragend kan werken. Weet iemand dat uit EIGEN ervaring?
 
Erwin H

Erwin H

01/02/2013 11:39:45
Quote Anchor link
Erwin H op 01/02/2013 11:34:52:
Benchmarken....

Waarom altijd anderen vragen als je het zelf gewoon kunt testen? Wat als je zo een antwoord krijgt van Kareltje 'ja dat is vertragend' en van Pietje 'nee hoor, ik merk er niets van'. Dan weet je nog geen donder. De enige die er werkelijk iets van kan zeggen ben jij, want jij kan het in JOUW situatie testen, met jouw omgeving en jouw classes. Zo moeilijk is het niet om het gewoon 100.000 keer uit te voeren en ervoor en erna een tijd te printen naar het scherm...

(en dus nog even op je eerste post reagerend, dan bedoel ik niet een test met alleen get of __get, maar in je volledige omgeving)
Gewijzigd op 01/02/2013 11:41:19 door Erwin H
 
Ozzie PHP

Ozzie PHP

01/02/2013 11:45:26
Quote Anchor link
Ik ben mijn omgeving nog aan het bouwen... dus ik kan alleen nog maar __get testen nu. Vandaar dat ik benieuwd ben naar ervaringen van anderen (alhoewel ik me ook voor een groot deel in jouw beargumentering kan vinden).
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 12:22:55
Quote Anchor link
Ozzie, eigenlijk heb ik al de derde mogelijkheid genoemd. Als je:

$container->base_url = 'www.mijnsite.nl';

wilt kunnen gebruiken, volstaat:

public $base_url;

De magische methode __get() gebruik je namelijk om ontoegankelijke eigenschappen alsnog toegankelijk te maken óf om de toegang op een of andere manier te regelen (bijvoorbeeld voor foutafhandeling). Je kunt __get() niet zomaar vergelijken met een get() en er is nog een alternatief.

Vraag is ook of je met __get() of een get() nu niet iets probeert te bereiken dat via de constructor moet lopen: het zetten van de gewenste $this->base_url. En omdat het hier om een root-URI c.q. hostnaam in een multi-site omgeving gaat, lijkt me het sowieso kritiek genoeg om het aan een constructor toe te vertrouwen.
 
Ger van Steenderen
Tutorial mod

Ger van Steenderen

01/02/2013 12:27:58
Quote Anchor link
Als je niet zo druk geweest was met topics over perfomance issues, was al een stuk verder geweest met bouwen.

De strftime functie is niet PHP's snelste functie maar om deze uit te voeren neemt gemiddeld 0.0005 seconden in beslag (gemeten waarden op een niet al te snel systeem). Nu heb jij het verschil tussen twee functies die een stuk 'simpeler' zijn, dus zal dat verschil nauwelijks meetbaar laat staan merkbaar zijn.
 
Ozzie PHP

Ozzie PHP

01/02/2013 13:27:48
Quote Anchor link
@ward: het gaat om een container die settings opslaat. Vantevoren weet je niet wat je erin stopt. Dat is het principe van zo'n container. Je kunt dus niet alles vantevoren 'predefinen'. Snap je het?

@Ger: als je het niet erg vindt bepaal ik graag zelf m'n eigen tempo. Ik doe liever iets doordacht dan ondoordacht.
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 15:00:36
Quote Anchor link
Ozzie PHP op 01/02/2013 13:27:48:
@ward: het gaat om een container die settings opslaat. Vantevoren weet je niet wat je erin stopt. Dat is het principe van zo'n container. Je kunt dus niet alles vantevoren 'predefinen'. Snap je het?
Een klasse waar je in kunt stoppen wat je maar wilt om het er later weer uit te kunnen halen, vind ik een OOP-nachtmerrie. Als je dat tot in het extreme doorvoert, is de uiterste consequentie namelijk dat je één klasse kunt gebruiken voor alles. En eigenlijk niet eens dat, want dan kun je elke applicatie of sessie gewone een lege container meegeven met $data = array() en daar alles instoppen.

Uit je voorbeelden leid ik af dat je meer concreets weet: je wilt niet slechts willekeurige settings opslaan, maar het gaat concreet om basis-URL's en paden. Bouw dan liever iets concreets en formaliseer de logica in je klassen.
 
Moose -

Moose -

01/02/2013 15:13:49
Quote Anchor link
Ward van der Put op 01/02/2013 09:15:18:
Een belangrijk verschil tussen __get() en een eigen getter is dat __get() automatisch wordt aangeroepen bij een eigenschap die ontoegankelijk is. In dit voorbeeld is dat bijvoorbeeld private $Gebruikersnaam:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Gebruiker
{
    private $Gebruikersnaam;

    public function construct($gebruikersnaam)
    {

        $this->Gebruikersnaam = $gebruikersnaam;
    }
}


/* Nieuwe gebruiker met een gebruikersnaam */
$g = new Gebruiker('Admin');
/**
 * Deze aanroep van een ontoegankelijke eigenschap strandt in een
 * Fatal Error: Cannot access private property Gebruiker::$Gebruikersnaam.
 */

echo $g->Gebruikersnaam;
?>


Door de fatale fout stopt het script resoluut. Maar misschien wil je dat helemaal niet en kan het script ook in leven blijven bij een ongeldige aanroep. Je kunt __get() dan bijvoorbeeld zo aan het werk zettten:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Gebruiker
{
    private $Gebruikersnaam;

    public function construct($gebruikersnaam)
    {

        $this->Gebruikersnaam = $gebruikersnaam;
    }


    public function __get($name)
    {

        return null;
    }
}


/**
 * De gebruikersnaam is nog steeds een ontoegankelijke eigenschap,
 * maar een onschuldige fout wordt onderdrukt.
 */

$g = new Gebruiker('Admin');
echo $g->Gebruikersnaam;
?>


Een magische methode zoals __get() wordt automatisch aangeroepen. Dat maakt magische methoden dus belangrijk voor alle aanroepen waarvoor je niet zelf expliciet een getter hebt geschreven. Dat geldt dan weer per definitie voor alle eigenschappen die geen getter hebben.

Ik zou __get() alleen niet gebruiken (of: misbruiken) om eigenschappen die je eerst als private of protected declareert alsnog toegankelijk te maken. Dan kun je de eigenschap beter public maken.


Lees je uberhaupt wel de vraag of kijk je alleen maar naar de titel?

@Ozzie ken je het try-it-and-see principe?
Gewijzigd op 01/02/2013 15:15:45 door Moose -
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 15:37:09
Quote Anchor link
Moose - op 01/02/2013 15:13:49:
Lees je uberhaupt wel de vraag of kijk je alleen maar naar de titel?

@Ozzie ken je het try-it-and-see principe?
Een vraag anoniem beantwoorden met twee wedervragen waarvan één een sneer en één een algemeenheid is, hoe zal ik het zeggen... makkelijk?

Natuurlijk lees ik de vraag, met Ozzie heb ik een prima verstandhouding. Maar ik denk dat hij hier iets impliciets probeert te doen wat je beter expliciet kunt maken.
 
Erwin H

Erwin H

01/02/2013 15:47:36
Quote Anchor link
Ward van der Put op 01/02/2013 15:00:36:
Een klasse waar je in kunt stoppen wat je maar wilt om het er later weer uit te kunnen halen, vind ik een OOP-nachtmerrie. Als je dat tot in het extreme doorvoert, is de uiterste consequentie namelijk dat je één klasse kunt gebruiken voor alles. En eigenlijk niet eens dat, want dan kun je elke applicatie of sessie gewone een lege container meegeven met $data = array() en daar alles instoppen.

Hoewel ik je gedachtegang begrijp, ben ik het er niet helemaal eens.
Zelf heb ik bijvoorbeeld een klasse die voor elke applicatie de GET danwel POST waardes uitleest (en checkt). Daarbij is het uiteraard onmogelijk om van te voren alle mogelijke parameters voor te definieren. Je zal dus een methode moeten hebben om elke mogelijke parameter, variabel, uit te lezen. Daarvoor is de __get methode een optie, of een get() functie.
Een config container is een andere optie (die Ozzie hier volgens mij gebruikt) waarbij het ook zeer zeker nuttig is om met zo'n methode in plaats van voorgedefinieerde getters te werken.

Wat ik echter nooit, maar dan ook echt nooit zou doen is:
Ward van der Put op 01/02/2013 12:22:55:
public $base_url;

Daarmee leg je namelijk afhankelijkheden tussen klassen die je heel moeilijk weer kan veranderen. Als je later eens dat property weg wil halen dan kan je al je klassen langs om te zoeken naar die ene klasse die direct het property aansprak. Gebruik je getters dan is dat heel wat makkelijker op te vangen.
 
Wouter J

Wouter J

01/02/2013 16:07:31
Quote Anchor link
Ward, ooit gehoord van een dependency container? Deze gaat alleen maar om met dingen die je in een klasse stopt en eruit haalt... En zo'n container wordt momenteel gezien als het nieuwste en beste design pattern om dependency injection te regelen.

Ozzie, sorry dat ik het zeggen moet, maar je klinkt de laatste tijd een beetje geïrriteerd. Ik denk ook dat wij iets minder goed jou topics beantwoorden dan eerst. Weet je waardoor dat komt? Dat komt omdat je de laatste tijd alleen maar performance topics opent. Laat ik voorop stellen dat ik het extreem goed vind dat je eerst alles uitdenkt en je zorgen maakt over de gebruiksgemak van je applicatie, maar je kunt ook overdrijven. Het verschil in merkbare performance doe je niet met dit soort onbelangrijke dingen, je hier zorgen over maken is nutteloos. Het performance verschil doe je later. Eerst script je je hele applicatie (nadat je hem hebt uitgedacht). Grote kans dat je dan denkt, nou dit gaat eigenlijk best snel. Dan hoef je je geen zorgen meer te maken. Kleine kans dat je denkt, tjonge jonge wat gaat die langzaam. In dat geval ga je her en der een beetje brenchmarken en de echte grote dingen aanpakken, zoals het cachen van configuratie instellingen en het verwijderen van grote queries. Dit soort dingen ga je nooit merken en nee, ook niet als je straks een zeer succesvolle website hebt.

En je stelt de laatste tijd ook wel veel vragen die je 123 kunt opzoeken op google. Misschien handig om daar ook wat tijd in te steken (in het gebruik of in het leren te gebruiken).
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 16:47:53
Quote Anchor link
Erwin H op 01/02/2013 15:47:36:
Wat ik echter nooit, maar dan ook echt nooit zou doen is:
Ward van der Put op 01/02/2013 12:22:55:
public $base_url;

Daarmee leg je namelijk afhankelijkheden tussen klassen die je heel moeilijk weer kan veranderen. Als je later eens dat property weg wil halen dan kan je al je klassen langs om te zoeken naar die ene klasse die direct het property aansprak. Gebruik je getters dan is dat heel wat makkelijker op te vangen.


Dat ben ik zeker met je eens. Alleen denk ik dat een gewillige setter die alles slikt en een vrijgevige getter die alles cadeau doet geen goed alternatief is:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class OverloadingClassFromHell
{
    private $data = array();

    public function __set($name, $value)
    {

        $this->data[$name] = $value;
    }


    public function __get($name)
    {

        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }
    }
}

?>


Nu hebben we weliswaar private $data maar ook een getter die alles weggeeft. Beter? En passant hebben we het wiel opnieuw uitgevonden en de werking van een array nagebouwd. Beter?

Maar wat we ook hebben gedaan, is de basis leggen voor iets dat we later per se niet willen, bijvoorbeeld:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
class CreditcardBetaling extends OverloadingClassFromHell
{
    private CreditcardNummer;
    private CreditcardVervaldatum;
}

?>


En dan het punt uit mijn eerste voorbeeld: je kunt een gerichte get() met specifieke functionaliteit niet zomaar vergelijken met de magische __get() met generieke functionaliteit. Natuurlijk kun je wel hun snelheid vergelijken, maar dan vergelijk je appels met peren.
 
Erwin H

Erwin H

01/02/2013 16:53:54
Quote Anchor link
Ward van der Put op 01/02/2013 16:47:53:
Dat ben ik zeker met je eens. Alleen denk ik dat een gewillige setter die alles slikt en een vrijgevige getter die alles cadeau doet geen goed alternatief is:

Dat ligt aan waar je het voor nodig hebt.
Ward van der Put op 01/02/2013 16:47:53:
Nu hebben we weliswaar private $data maar ook een getter die alles weggeeft. Beter? En passant hebben we het wiel opnieuw uitgevonden en de werking van een array nagebouwd. Beter?

Zonder meer beter, met het oog op de toekomst. Nu heb je het namelijk helemaal in de hand om je getter en setter aan te passen zoveel je wilt, zonder dat je er ooit een ander deel van je applicatie mee in gevaar brengt.
Ward van der Put op 01/02/2013 16:47:53:
En dan het punt uit mijn eerste voorbeeld: je kunt een gerichte get() met specifieke functionaliteit niet zomaar vergelijken met de magische __get() met generieke functionaliteit. Natuurlijk kun je wel hun snelheid vergelijken, maar dan vergelijk je appels met peren.

Wel als je die get() gebruikt voor alle waardes (dus get($name)). Dan is het net zo generiek als __get($name). Ik zie echt geen verschil ertussen.

Even als disclaimer, ik probeer niet te zeggen dat een generieke getter beter is dan een serie van specifieke getters voor vooraf benoemde properties. Als je van te voren weet welke properties je hebt, dan is het maken van specifieke getters voor elk property vanuit alle punten beter wat mij betreft. Het gaat nu juist om de momenten waarop dat dus niet mogelijk is.
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 17:38:46
Quote Anchor link
Erwin H op 01/02/2013 16:53:54:
Ward van der Put op 01/02/2013 16:47:53:
En dan het punt uit mijn eerste voorbeeld: je kunt een gerichte get() met specifieke functionaliteit niet zomaar vergelijken met de magische __get() met generieke functionaliteit. Natuurlijk kun je wel hun snelheid vergelijken, maar dan vergelijk je appels met peren.

Wel als je die get() gebruikt voor alle waardes (dus get($name)). Dan is het net zo generiek als __get($name). Ik zie echt geen verschil ertussen.

Een get() moet je gericht aanroepen, een __get() wordt als magische methode automatisch aangeroepen zodra je een eigenschap aanroept die je eerder met private of protected ontoegankelijk hebt verklaard. Het ene is een positive, het andere een false negative. Min keer min is inderdaad ook plus, maar het is toch niet hetzelfde.

En dat is een wezenlijk verschil: de methode __get() wordt automatisch afgevuurd zodra eigenschappen worden benaderd waartoe je eerder toegang verboden hebt. Dat maakt __get() een onlogische keuze: met toegang tot eigenschappen die ontoegankelijk zijn verklaard, ondermijn je mogelijk je objectmodel. En die toegang is nog tweederangs ook: de magische methode __get() wordt pas afgevuurd nadat de toegang is geweigerd.
 
Wouter J

Wouter J

01/02/2013 17:47:48
Quote Anchor link
Ward, de __get() wordt ook aangeroepen als de propertie niet bestaat.
 
Ward van der Put
Moderator

Ward van der Put

01/02/2013 17:52:21
Quote Anchor link
Wouter J op 01/02/2013 17:47:48:
Ward, de __get() wordt ook aangeroepen als de propertie niet bestaat.
Dat verandert de argumentatie toch niet?
 
Erwin H

Erwin H

01/02/2013 17:55:33
Quote Anchor link
He he, een puritein :-)
En dat bedoel ik zeker niet als een scheldwoord, dat ben ik zelf namelijk ook...

Ok, ja, de manier waarop de get() functie zou worden aangeroepen is anders dan de manier waarop de __get() functie wordt aangeroepen, zonder meer. Vraag is, heeft dat bepaalde consequenties?

Waar ik echter vind dat je te ver gaat is bij deze twee opmerkingen:
Ward van der Put op 01/02/2013 17:38:46:
zodra je een eigenschap aanroept die je eerder met private of protected ontoegankelijk hebt verklaard.
...
een onlogische keuze: met toegang tot eigenschappen die ontoegankelijk zijn verklaard

Dat is strict genomen misschien waar (kijkende naar private/protected/public), maar welk toegangsniveau wordt gegeven is voor mij totaal niet afhankelijk van wie het mag zien. Dat toegangsniveau is voor mij (voor een property) altijd private simpelweg omwille van de robuustheid van je applicatie. Toegang wordt geregeld door het wel of niet hebben van een getter danwel setter. Sterker, ik kan ook een getter of setter hebben voor een property dat fysiek helemaal niet bestaat. Bovenstaande opmerkingen vind ik dus enigszins overdreven.
 

Pagina: 1 2 3 4 volgende »



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.