[PHP5] __call

Pagina: 1
Acties:

  • TangLeFuzZ
  • Registratie: Juni 2001
  • Laatst online: 15-10-2025
Hey,

ik kwam op de PHP5 pagina __call tegen.

Bij het commentaar stond de volgende functie, die vrij handig is wanneer je erg veel get functies gebruikt in een class:

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
   ##    PORTABLE: use this __call function in any class
   function __call($val, $x)
       {
       # see if they're calling a getter method - and try to guess the variable requested
       if(substr($val, 0, 4) == 'get_')
           {
           $varname = substr($val, 4);
           }
       elseif(substr($val, 0, 3) == 'get')
           {
           $varname = substr($val, 3);
           }
       else
           {
           die("method $val does not exist\n");
           }
       # now see if that variable exists:
       foreach($this as $class_var=>$class_var_value)
           {
           if(strtolower($class_var) == strtolower($varname))
               {
               return $class_var_value;
               }
           }
       return false;
       }


Ik wilde graag dat hij ook set functies ondersteunde, dus ik heb er het volgende van gemaakt:

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
    ##    PORTABLE: use this __call function in any class
    public function __call($val, $x)
    {
        if(substr($val, 0, 3) == 'get')
        {
            $varname = substr($val, 3);
            # now see if that variable exists:
            foreach($this as $class_var=>$class_var_value)
            {
                if(strtolower($class_var) == strtolower($varname))
                {
                    return $class_var_value;
                }
            }
        }
        elseif(substr($val, 0, 3) == 'set')
        {
            $varname = substr($val, 3);
            foreach($this as $class_var=>$class_var_value)
            {
                if(strtolower($class_var) == strtolower($varname))
                {
                    $class_var = $x[0];
                }
            }
        }
        else
        {
            die("method $val does not exist\n");
        }
        

        return false;
    }


Hij werkt nu wel, maar ik weet niet of het nu wel veilig is e.d... daar heb ik te weinig inzicht in __call en PHP5 voor, misschien dat jullie zien of hier iets mis mee is?

  • ludo
  • Registratie: Oktober 2000
  • Laatst online: 01-03 18:17
Nou als je dit op deze manier implementeert kan je veel makkelijker alle attributen in je klassen public maken, want dat is wat je nu (via een omweg) eigenlijk bereikt hebt. Maar of dat nou iets is dat je graag wilt hebben... Het lijkt me meestal niet gewenst dat alle attributen van een klasse door andere objecten gewijzigd kunnen worden.

Maar persoonlijk zou ik iets dergelijks niet willen gebruiken. Ik vind dat het voor een grote onduidelijkheid zorgt in een klasse. De get en set methods die ik nodig heb wil ik gewoon zelf implementeren, en vaak wil ik hierbij ieder attribuut ook nog op een eigen manier 'getten' of 'setten' (bijvoorbeeld validaties, addslashes, etc.).

[ Voor 3% gewijzigd door ludo op 22-02-2005 19:56 ]


Verwijderd

Waarom in dit geval __call() gebruiken als we __get() en __set() hebben?
Zie http://php.net/oop5.overloading

[ Voor 3% gewijzigd door Verwijderd op 22-02-2005 20:00 . Reden: ooh, je moet hem tussen twee url tags in gooien :} ]


  • JHS
  • Registratie: Augustus 2003
  • Laatst online: 04-01 15:49

JHS

Splitting the thaum.

Ludo: Is er niet een manier om dat alleen toe te staan als het een public value is ? Ik kan er geen bedenken / vinden .

DM!


  • MisterData
  • Registratie: September 2001
  • Laatst online: 11-05 22:29
JHS schreef op dinsdag 22 februari 2005 @ 20:23:
Ludo: Is er niet een manier om dat alleen toe te staan als het een public value is ? Ik kan er geen bedenken / vinden .
Waarschijnlijk kun je wel ergens via de PHP5 reflection zien welke members een class heeft en wat voor accesstype die hebben?

  • ludo
  • Registratie: Oktober 2000
  • Laatst online: 01-03 18:17
JHS schreef op dinsdag 22 februari 2005 @ 20:23:
Ludo: Is er niet een manier om dat alleen toe te staan als het een public value is ? Ik kan er geen bedenken / vinden .
Ja dat kan waarschijnlijk wel door ze op te vragen met behulp van de functies get_class_vars() en get_object_vars().

Maar als je een attribute public maakt, waarom zou je het dan nog met behulp van een methode als in de TS willen proberen te setten/getten? Dan kan je toch veel makkelijker meteen het attribute opvragen/wijzigen aangezien het toch public is? :) __call voegt in bovenstaande toepassing alleen maar extra overhead toe.

[ Voor 5% gewijzigd door ludo op 22-02-2005 21:30 ]


  • Sybr_E-N
  • Registratie: December 2001
  • Laatst online: 13-05 20:39
Die manier waarop __call in de topicstart is geimplementeerd lijkt me een beetje overkill, __get en __set kunnen daar beter voor gebruikt worden. Maar waarvoor zou je __call() dan wel fatsoenlijk kunnen gebruiken?

Je kunt dus methoden aanroepen met parameters welke je niet hebt geimplementeerd. Op basis van de methodnaam zou je dus bepaalde acties kunnen uitvoeren.Ik zou me er wel iets bij voor kunnen stellen als je het gebruikt als je aan het devven bent. Dan kun je eventuele verkeerde aanroepen van methoden afvangen door bijvoorbeeld een Exception te throwen.

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
Ik vind dit gebruik van __call dus juist wel erg goed. Je kunt dan voor get en set functies basis functionaliteit hebbben, en mocht je het nodig hebben, dan kun je een bepaald method toch implementeren voor extra functionaliteit. Zo verberg je dus de implementatie netjes voor de client en kun je aanpassingen maken zonder de clients ook te hoeven veranderen. __set en __get is smerig imo, omdat deze dat niet toestaan zonder dit in de functies zelf te gaan zetten, waardoor je een erg onsamenhangende functie krijgt.

Noushka's Magnificent Dream | Unity


  • ludo
  • Registratie: Oktober 2000
  • Laatst online: 01-03 18:17
Michali schreef op woensdag 23 februari 2005 @ 10:07:
Ik vind dit gebruik van __call dus juist wel erg goed. Je kunt dan voor get en set functies basis functionaliteit hebbben, en mocht je het nodig hebben, dan kun je een bepaald method toch implementeren voor extra functionaliteit. Zo verberg je dus de implementatie netjes voor de client en kun je aanpassingen maken zonder de clients ook te hoeven veranderen.
De implementatie is sowieso niet zichtbaar zijn voor de client. Maar door __call op deze manier te gebruiken wordt het voor een client moeilijk om de interface van een klasse te zien. En met deze implementatie van __call is het grootste probleem dat álle attributen van een klasse door de client gewijzigd kunnen worden. En dat is meestal zeer ongewenst. Dit vind ik het grootste nadeel van deze methode. Maar ook het feit dat er de interface niet meteen te achterhalen is door naar de buitenkant van de klasse te kijken is een zeer groot nadeel.

En even een voorbeeld, wat is de meerwaarde van
PHP:
1
2
3
4
5
6
7
8
9
10
class A
{
  private $foo;
  private $bar;

  public function __call($val, $x)
  {
    // Implementatie van TS
  }
}
t.o.v.
PHP:
1
2
3
4
5
class B
{
  public $foo;
  public $bar;
}
:?
En stel nu dat ik niet wil dat $bar in klasse A gewijzigd/opgevraagd mag worden door andere klassen, dan zit ik met een probleem want __call doet dat gewoon als een andere klasse dat probeert met een get/set method...

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
ludo schreef op woensdag 23 februari 2005 @ 10:21:
[...]
De implementatie is sowieso niet zichtbaar zijn voor de client. Maar door __call op deze manier te gebruiken wordt het voor een client moeilijk om de interface van een klasse te zien. En met deze implementatie van __call is het grootste probleem dat álle attributen van een klasse door de client gewijzigd kunnen worden. En dat is meestal zeer ongewenst. Dit vind ik het grootste nadeel van deze methode. Maar ook het feit dat er de interface niet meteen te achterhalen is door naar de buitenkant van de klasse te kijken is een zeer groot nadeel.
Dat probleem heb je met __get en __set ook. Overigens kun je met een simpele check gewoon regelen welke variabelen wel of niet bewerkt mogen worden. En het is niet voor niets dat door ieder OO boek of aanhanger gewoon verboden wordt om velden publiekelijk toegankelijk te maken.
En even een voorbeeld, wat is de meerwaarde van
PHP:
1
2
3
4
5
6
7
8
9
10
class A
{
  private $foo;
  private $bar;

  public function __call($val, $x)
  {
    // Implementatie van TS
  }
}
t.o.v.
PHP:
1
2
3
4
5
class B
{
  public $foo;
  public $bar;
}
:?
En stel nu dat ik niet wil dat $bar in klasse A gewijzigd/opgevraagd mag worden door andere klassen, dan zit ik met een probleem want __call doet dat gewoon als een andere klasse dat probeert met een get/set method...
Zoals ik al zei, je kunt hier een check voor maken. Stel dat je nu 50 clients hebt die deze variabelen zo gebruiken. En je komt er achter dat deze waardes eigenlijk tot een andere classes behoren en je wilt ze verplaatsen naar een andere class? dat gaat dan niet omdat je er aan vast zit zo. Juist door een method te gebruiken kun je verbergen wat er achter de schermen gebreurt en kun je delegaten naar wat je wilt. Het is gewoon veel gemakkelijk te refactoren en uit te breiden. Je kunt dat natuurlijk ook kiezen voor __get of __set, maar dan zou je jezelf tegenspreken omdat je de interface niet kunt zien. Welke je overigens altijd gewoon moet documenteren, dan zit je niet met zulke problemen.

Noushka's Magnificent Dream | Unity


  • ludo
  • Registratie: Oktober 2000
  • Laatst online: 01-03 18:17
Michali schreef op woensdag 23 februari 2005 @ 10:28:
[...]

Dat probleem heb je met __get en __set ook. Overigens kun je met een simpele check gewoon regelen welke variabelen wel of niet bewerkt mogen worden. En het is niet voor niets dat door ieder OO boek of aanhanger gewoon verboden wordt om velden publiekelijk toegankelijk te maken.
Maar hoe wil jij die check dan uit gaan voeren? Een array oid in __call() zetten met de namen van de attributen die bewerkt mogen worden/zichtbaar zijn?
En dat probleem heb je met __set() en __get() inderdaad ook, die zou ik dus ook nooit gebruiken voor dit soort zaken :)
Zoals ik al zei, je kunt hier een check voor maken. Stel dat je nu 50 clients hebt die deze variabelen zo gebruiken. En je komt er achter dat deze waardes eigenlijk tot een andere classes behoren en je wilt ze verplaatsen naar een andere class? dat gaat dan niet omdat je er aan vast zit zo. Juist door een method te gebruiken kun je verbergen wat er achter de schermen gebreurt en kun je delegaten naar wat je wilt. Het is gewoon veel gemakkelijk te refactoren en uit te breiden. Je kunt dat natuurlijk ook kiezen voor __get of __set, maar dan zou je jezelf tegenspreken omdat je de interface niet kunt zien. Welke je overigens altijd gewoon moet documenteren, dan zit je niet met zulke problemen.
Als je er achter zou komen dat een attribuut eigenlijk bij een andere klasse hoort, kan je die toch overhevelen naar die andere klasse en de bijbehorende get/set methodes meekopiëren?
Als je __call() gebruikt met een check zul je er voor moeten zorgen dat je ze uit de __call() van class A haalt en ze weer implementeert in class B. Ik zie dus niet waarom dit makkelijker zou zijn bij het refactoren of bij het uitbreiden van een klasse, het lijkt mij juist een stuk omslachtiger.

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
ludo schreef op woensdag 23 februari 2005 @ 15:09:
[...]
Maar hoe wil jij die check dan uit gaan voeren? Een array oid in __call() zetten met de namen van de attributen die bewerkt mogen worden/zichtbaar zijn?
En dat probleem heb je met __set() en __get() inderdaad ook, die zou ik dus ook nooit gebruiken voor dit soort zaken :)
Bijvoorbeeld ja, hoewel ik het nooit zal gebruiken :)
Als je er achter zou komen dat een attribuut eigenlijk bij een andere klasse hoort, kan je die toch overhevelen naar die andere klasse en de bijbehorende get/set methodes meekopiëren?
Als je __call() gebruikt met een check zul je er voor moeten zorgen dat je ze uit de __call() van class A haalt en ze weer implementeert in class B. Ik zie dus niet waarom dit makkelijker zou zijn bij het refactoren of bij het uitbreiden van een klasse, het lijkt mij juist een stuk omslachtiger.
Jij had het niet over __get of __set. Je had het over het publiekelijk beschikbaar maken van de velden. Dan kun je ze niet snel overzetten. Als je een private veld met een aparte get en set functie hebt, dan kan het wel gemakkelijk. En met __call kun je ook delegaten, maar dat vind ik niet zo netjes eigenlijk.

Het gebruik van __call heeft bij mij gewoon de voorkeur simpel om de reden dat je niet kunt onderscheiden of de call nou wordt afgehandelt door __call of door een 'echte' functie.

[ Voor 8% gewijzigd door Michali op 23-02-2005 15:22 ]

Noushka's Magnificent Dream | Unity


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Als we __call hiervoor gaan misbruiken, gaan we __set dan ook misbruiken voor functie aanroepen? Op een dergelijke manier? Waarom schijnt iedereen zo voor het gebruik van dingen te zijn waar ze voor bedoelt zijn als het om HTML gaat, maar zodra het om PHP gaat, weer niet? Methods definieren nogsteeds gedrag, geen eigenschappen.
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class Foo
{
    public function __set($Method, $Parameters)
    {
        call_user_func(array($this, $Method), $Parameters);
    }

    public function bar($Var)
    {
        echo 'Foo::Bar($Var) is aangeroepen met ' . print_r($Var, true) . "\n";
    }
}

$F = new Foo();
$F->Bar = 'x';
$F->Bar = array('x', 'y');
?>
Sybr_E-N schreef op dinsdag 22 februari 2005 @ 21:34:
Maar waarvoor zou je __call() dan wel fatsoenlijk kunnen gebruiken?
Je zou het kunnen gebruiken voor een implementatie van het proxy design pattern, om functies door te sluizen naar de class die ze op dat moment af hoort te handelen.

[ Voor 5% gewijzigd door PrisonerOfPain op 23-02-2005 15:40 ]


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
PrisonerOfPain schreef op woensdag 23 februari 2005 @ 15:37:
Als we __call hiervoor gaan misbruiken, gaan we __set dan ook misbruiken voor functie aanroepen? Op een dergelijke manier? Waarom schijnt iedereen zo voor het gebruik van dingen te zijn waar ze voor bedoelt zijn als het om HTML gaat, maar zodra het om PHP gaat, weer niet? Methods definieren nogsteeds gedrag, geen eigenschappen.
__call gebruiken voor het aanroepen van get en set functies vind ik geen misbruik iig. Ik zou er hoe dan ook een method van maken. Of bedoelde je dat niet?

Noushka's Magnificent Dream | Unity


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Michali schreef op woensdag 23 februari 2005 @ 16:03:
[...]

__call gebruiken voor het aanroepen van get en set functies vind ik geen misbruik iig.
Waarom niet? Daar heb je __get en __set toch voor?
Ik zou er hoe dan ook een method van maken. Of bedoelde je dat niet?
Dat is nou juist 't punt, waarom een method? Of in iedergeval, waarom moet de client weten dat het om een method gaat? Dat is toch helemaal niet relevant voor de client? Roep vanuit __get of __set dan de betreffende getter/setter aan. 'Best' of both worlds als je het mij vraagt.

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
PrisonerOfPain schreef op woensdag 23 februari 2005 @ 18:28:
[...]

Waarom niet? Daar heb je __get en __set toch voor?

[...]

Dat is nou juist 't punt, waarom een method? Of in iedergeval, waarom moet de client weten dat het om een method gaat? Dat is toch helemaal niet relevant voor de client? Roep vanuit __get of __set dan de betreffende getter/setter aan. 'Best' of both worlds als je het mij vraagt.
Maar ik vind eigenlijk dat je met __get en __set te veel info weg geeft. Het hoeft namelijk helemaal geen veld te zijn die je ophaalt. Als je gebruik maakt van __call hoeft het niet perse __call te zijn de een functie aanroep afhandelt. Het kan net zo goed een delegate zijn naar een geaggregeerd object. En het mooie van __call is dat je gewoon een method kan toevoegen waardoor __call dan nooit meer aangeroepen wordt bij die functie naam. Je hoeft __call dus zelf niet te vervuilen met een switch oid. je voegt gewoon een extra functie toe en daarma overload je als het ware de functionaliteit van __call. Met __get en __set is dit niet mogelijk of je moet wel een extra check inbouwen. En daar hou ik niet van.

Noushka's Magnificent Dream | Unity


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Michali schreef op woensdag 23 februari 2005 @ 19:05:
Maar ik vind eigenlijk dat je met __get en __set te veel info weg geeft. Het hoeft namelijk helemaal geen veld te zijn die je ophaalt.
Hoe bedoel je, als het om een eigenschap/property gaat zul je IMHO geen getter/setter method expliciet aan hoeven roepen. Als dat nodig is, zul je daar zelf voor moeten zorgen dat dat transparant gebeurt voor de gebruiker, of zorgen dat de programmeertaal dat voor je doet (C#). En wat voor informatie geef je trouwens weg? nieuwsgierig
Als je gebruik maakt van __call hoeft het niet perse __call te zijn de een functie aanroep afhandelt. Het kan net zo goed een delegate zijn naar een geaggregeerd object. En het mooie van __call is dat je gewoon een method kan toevoegen waardoor __call dan nooit meer aangeroepen wordt bij die functie naam.
Hetzelfde geld voor variabelen, declareer jij liever een public $Color (onderstaande voorbeeld). Ga je gang, __get en __set worden niet meer aangeroepen.
Je hoeft __call dus zelf niet te vervuilen met een switch oid. je voegt gewoon een extra functie toe en daarma overload je als het ware de functionaliteit van __call. Met __get en __set is dit niet mogelijk of je moet wel een extra check inbouwen. En daar hou ik niet van.
Mijn idee was dus om het op de volgende manier te doen. Als je public variabele wilt hebben is dat ook gewoon mogenlijk. Zelf vind ik deze manier netter omdat het toch om een property gaat, niet om gedrag.
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
class Bar
{
    private $myColor = 'green';

    public function __get($Property)
    {
        $Method = '__get' . $Property;

        if($this->methodExists($Method))
        {
            return $this->$Method();
        }
        elseif (isSet($this->{'my' . $Property}))
        {
            return $this->{'my' . $Property};
        }
    }

    public function __set($Property, $Value)
    {
        $Method = '__set' . $Property;

        if($this->methodExists($Method))
        {
            if(!is_array($Value))
            {
                $Value = array($Value);
            }

            call_user_func_array(array($this, $Method), $Value);
        }
        elseif (isSet($this->{'my' . $Property}))
        {
            $this->{'my' . $Property} = $Value;
        }
    }

    private function methodExists($Method)
    {
        $failed = false;

        try
        {
            new ReflectionMethod($this, $Method);
        }
        catch(ReflectionException $e)
        {
            $failed = true;
        }

        return !$failed;
    }

    private function __setColor($Color)
    {
        if(in_array($Color, array('green', 'blue')))
        {
            $this->myColor = $Color;
        }
    }
}

$X = new Bar();
echo $X->Color;
echo $X->Color = 'blue';


Zoals je ziet worden __get en __set ook niet vervuild door een switch, ze hebben uberhaupt geen wetenschap van wat er met die property gedaan moet worden.
Het kan zijn dat ik er helemaal naast zit, hoor ;)

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 22-03 18:12
PrisonerOfPain schreef op woensdag 23 februari 2005 @ 21:08:
[...]

Hoe bedoel je, als het om een eigenschap/property gaat zul je IMHO geen getter/setter method expliciet aan hoeven roepen. Als dat nodig is, zul je daar zelf voor moeten zorgen dat dat transparant gebeurt voor de gebruiker, of zorgen dat de programmeertaal dat voor je doet (C#). En wat voor informatie geef je trouwens weg? nieuwsgierig
Je geeft info weg dat het om het ophalen of bewerken van een veld gaat.
Hetzelfde geld voor variabelen, declareer jij liever een public $Color (onderstaande voorbeeld). Ga je gang, __get en __set worden niet meer aangeroepen.
Ik zou nooit een veld publiek declareren.. heb ik dat ergens gezegd dan? Dat is gewoon smerig en daarmee maak je het nog lelijker.
Mijn idee was dus om het op de volgende manier te doen. Als je public variabele wilt hebben is dat ook gewoon mogenlijk. Zelf vind ik deze manier netter omdat het toch om een property gaat, niet om gedrag.
PHP:
1
// code

Zoals je ziet worden __get en __set ook niet vervuild door een switch, ze hebben uberhaupt geen wetenschap van wat er met die property gedaan moet worden.
Het kan zijn dat ik er helemaal naast zit, hoor ;)
Waar het om gaat is dat ik niet aan de client wil verklappen hoe ik aan de informatie kom. Mischien is color wel een gemiddelde van alle contained objecten en niet direct een veld. Natuurlijk kun je dit oplossen door een extra check binnen de __get te zetten, maar dat vind ik persoonlijk niet zo netjes. Of je kunt achteraf nog een method toevoegen, maar dan heb je weer de kans dat je bugs introduceert als je al de kans hebt om een wijziging in alle clients te maken. Stel dat je __call had gebruikt, dan heb je nog de optie om een extra method toe te voegen met de naam getColor. Daarbinnen zet je dan de code die het gemiddelde uitrekent.

En over die switch, stel dat je nu een veld hebt die niet gewijzigd mag worden? Of als je een veld hebt die extra code vereist? Dan ga je toch al snel met een switch werken of een if elseif elseif etc. constructie (maakt niet uit).

[ Voor 6% gewijzigd door Michali op 24-02-2005 12:10 ]

Noushka's Magnificent Dream | Unity

Pagina: 1