[PHP] Een class uitbreiden met functies

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
Hoi,

Beseffende dat het heel erg basic is wat ik wil, en het antwoord vast heel simpel is, wil ik het volgende probleem aan jullie voorleggen:

Ik heb een class die een aantal variabelen en een aantal functies bevat:

main.php:
PHP:
1
2
3
4
5
6
7
class Test {

private $naam;

function verander_naam ( $naam ) { $this->naam = $naam; }

}


En ik heb (in een ander bestandje) een uitbreiding op deze class gemaakt:

extension.php
PHP:
1
function bereken_lengte_naam() { return strlen( $this->naam); }


Nu wil ik vanuit main.php deze extra functionaliteit invoegen in de bestaande class. Het leifst met een functie als

PHP:
1
function add_plugin ( $filename ) { ... }


Ok, het is een beetje een idioot voorbeeldje natuurlijk, maar het gaat om het idee. Ik wil de class main.php nu als volgt kunnen gebruiken:

index.php
PHP:
1
2
3
4
$main = new Test();
$main->verander_naam("Bassie");
$main->add_plugin("extension.php");
$lengte = $main->bereken_lengte_naam();


Ik heb al geprobeerd van extension.php een class te maken, omdat dat me het mooiste lijkt, maar die kan dan natuurlijk niet bij $naam, ook niet via parent::$naam.

Een class met extends werkt niet omdat er dan een nieuw object Test wordt aangemaakt, waardoor we "Bassie" kwijt zijn.

Gewoon includen lukt niet, omdat de functie bereken_lengte_naam dan niet toegankelijk is vanuit index.php.

Als laatste optie zie ik nog de mogelijkheid om $main als GLOBAL te zetten zodat ik er vanuit extension.php bijkan, maar dat lijkt me een erg ranzige oplossing. Weet iemand iets beters?

Alvast bedankt!

Acties:
  • 0 Henk 'm!

  • daniëlpunt
  • Registratie: Maart 2004
  • Niet online

daniëlpunt

monkey's gone to heaven

Je kan de class wel extenden, maar dan maak je de property $naam protected in plaats van private.

Private is alleen toegankelijk voor instanties van het object, protected ook voor instanties van subclasses. :)

Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

ik ben niet zeker van PHP, maar in de meeste talen breid je classes uit door er van te deriven. Je krijgt dan een samengevoegde class, met de functies van zowel de base als de derived class.

in principe kan je (zover ik van PHP weet) je prima dit doen:

class base { function veranderNaam() { /*bla*/ } }
class derived : extends /*sp?*/ base { function ander() { /*foo*/ } }

-niks-


Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
@super-muffin: Als ik extend, zelfs met $naam als protected, en ik vraag $naam op in extension.php dan is ie leeg...

@MLM Als ik extension.php als class Test laat extenden, dan maakt extension.php NIET gebruik van dezelfde instance, maar wordt er een nieuwe instance aangemaakt, met een lege $naam.

[ Voor 52% gewijzigd door xilent_xage op 20-06-2008 19:53 ]


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Wat je kunt doen is ofwel een typische plugin class maken zoals dit:
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
<?php
class Test{
    private $name;
    
    public function verander_naam($naam) {
        $this->naam = $naam;
    }
    
    public function add_plugin(Plugin $plugin){
        $plugin->execute($this);
    }
}

interface Plugin{
    public function execute(Test $test);
}

class Foo implements Plugin{
    public function execute(Test $test){
        $test->verander_naam('bluh');
    }
}

$t = new Test();
$t->add_plugin(new Foo());


Of je pakt een klassieke decorator zoals dit:

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
<?php
interface ITest{
    public function do_something();
}

class Test implements ITest{
    public function do_something(){
        return $this->naam;
    }
}

class Test_Bold implements ITest{
    private $test;
    
    public function __construct(ITest $test){
        $this->test = $test;
    }
    
    public function do_something(){
        return '<b>' . $this->test->do_something() . '</b>';
    }
}

$t = new Test_Bold(new Test());
echo $t->do_something();


Of kan het handig zijn om Mixins in PHP te emuleren:

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
class Mixin
{
    private $me;
    
    public function setMe($newThis)
    {
        $this->me = $newThis;
    }
    
    public function __get($key)
    {
        return $this->me->$key;
    }
    
    public function __call($method, $arguments)
    {
        return call_user_func_array(
            array($this->me, $method),
            $arguments
        );
    }
    
    public function __set($key, $value)
    {
        $this->me->$key = $value;
    }
}

class Object
{
    private $mixinLookup_ = array();

    public function __construct()
    {
        if(is_array($this->mixins))
        {
            foreach($this->mixins as $mixin)
            {
                $methods = get_class_methods($mixin);
                foreach((array)$methods as $method)
                    $this->mixinLookup_[$method] = new $mixin;
            }
        }
    }

    public function __call($method, $arguments)
    {
        if(isset($this->mixinLookup_[$method]))
        {
            $this->mixinLookup_[$method]->setMe($this);
            return call_user_func_array(
                array($this->mixinLookup_[$method], $method),
                $arguments
            );
        }
    }
}

class Boldable extends Mixin
{
    public function bold()
    {
        return "<b>" . $this->toString() . "</b>";
    }
}

class Nixim extends Object
{
    protected $mixins = array("Boldable");

    public function toString()
    {
        return "Nixim";
    }
}

$n = new Nixim();
echo $n->bold();


De decorator is vooral handig als je dynamisch de functionaliteit van de class wilt veranderen terwijl dat transparant moet blijven voor (bijvoorbeeld) reeds bestaande classes. De plugin architectuur heeft als voordeel dat het misschien makkelijker te gebruiken is met je huidige opzet. De mixins hebben als voordeel dat je arbitraire functies aan je classes toe kunt voegen en als je de flow wat aanpast (voornamelijk het verplaatsen van het een en ander uit de constructor van Object naar een add_plugin functie) dan kun je ook vrij ver komen. Denk dat 't nog het dichtste in de buurt ligt van je originele usecase.

[ Voor 39% gewijzigd door PrisonerOfPain op 20-06-2008 20:25 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Een class extenden zou werken... Maar je zou moeilijk meerdere "plugins" kunnen toevoegen
Dan is er runkit_method_add, maar dat is nergens standaard geinstalleerd, en wil je waarschijnlijk toch niet gebruiken ;)

Hier een andere optie. Bijna transparant, alleen in de uiteindelijke plugin classes zou je dan nog $this->base-> moeten gebruiken, en de members van de base class die je extend moeten public zijn. Maar met reflection/references zou je dit ook nog weg kunnen werken.

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
<?php
abstract class base {
    private $_plugin = array();
    private $_method = array();
    
    function extend($p) {
        $c = get_class($this);
        
        $filename = $c."/plugins/".$p.".plugin.php";
        $classname = $c."_".$p;
        
        @include_once($filename);
        if (!class_exists($classname, false))
            throw new Exception("Kan plugin ".$c."/".$p." niet laden.");
        
        $r = new ReflectionClass($classname);
        foreach ($r->getMethods() as $m) {
            if (array_key_exists($m->name, $this->_method)) {
                throw new Exception("Methode ".$m->name." bestaat al");
                continue;
            }
            
            $this->_method[$m->name] = $p;
        }
        
        $this->_plugin[$p] = new $classname($this);
    }
    
    function __call($m, $args) {
        if (method_exists($this, $m))
            return;
        
        if (!array_key_exists($m, $this->_method))
            throw new Exception("Onbekende methode ".$m);
        
        $p = $this->_method[$m];
        call_user_func_array(array($this->_plugin[$p], $m), $args);
    }
}

abstract class plugin {
    protected $base;
    
    function __construct(base $base) {
        $this->base = $base;
    }
}

class aap extends base {
    public $naam;
    
    function __construct($naam) {
        $this->naam = $naam;
    }
    
    function banaan() {
        echo $this->naam.": Mmmm!\n";
    }
}

class aap_dans extends plugin {
    function dans() {
        echo $this->base->naam." doet de apendans!\n";
    }
}

$aap = new aap("King Kong");
$aap->extend("dans");

$aap->banaan();
$aap->dans();
?>


Edit: je kan dus get_class_methods() ipv ReflectionClass gebruiken zoals PrisonerOfPain ook in zijn voorbeeld gebruikte. Het is dan ook een paar maandjes geleden sinds ik voor het laatst gePHPd heb ;)
Edit2: PrisonerOfPain, ik gaf dan ook alleen aan dat de runkit extensie bestaat, als je iets verder leest zie je ook mijn andere oplossing, die enigzins lijkt op jouw mixins.

Als je voor alle instances van een class dezelfde "plugins" wil gebruiken zou je in dit voorbeeld dingen natuurlijk ook static kunnen maken en van te voren
aap::extend("dans");
kunnen gebruiken.

[ Voor 11% gewijzigd door Verwijderd op 20-06-2008 20:30 ]


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Verwijderd schreef op vrijdag 20 juni 2008 @ 20:21:
EEn class extenden zou werken... Maar je zou moeilijk meerdere "plugins" kunnen toevoegen
Dan is er runkit_method_add, maar dat is nergens standaard geinstalleerd, en wil je waarschijnlijk toch niet gebruiken ;)
Kijk eens naar de mixin oplossing die ik hierboven er net achteraan editte; die heeft de nogal instabiele runkit extensie niet nodig en bereikt in dit geval praktisch hetzelfde. Het is een oplossing die ik ooit op de ATK blog gepost heb, maar die is al een tijdje down; vandaar de repost. Persoonlijk is het iets waar ik nog vrijwel dagelijks profijt van heb in m'n view layer :).
Verwijderd schreef op vrijdag 20 juni 2008 @ 20:21:
Edit2: PrisonerOfPain, ik gaf dan ook alleen aan dat de runkit extensie bestaat, als je iets verder leest zie je ook mijn andere oplossing, die enigzins lijkt op jouw mixins.
Klopt. Het enige verschil is, practisch gezien, dat je nog een __call, __set en __get functie aan de plugin class toe zou kunnen voegen om de $this->base te elimineren en gewoon $this te kunnen gebruiken.

[ Voor 31% gewijzigd door PrisonerOfPain op 20-06-2008 20:34 ]


Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
En ik maar denken dat het iets heel simpels was :)

Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
Wat jammer dat het niet mogelijk is om zowel de base als mixin/plugin class van een __construct() te voorzien! Geen van de voorbeelden werkt dan nog... De code gaat me net iets boven de pet, kan iemand ff verklappen waarom het niet werkt, en hoe het wel kan?

Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Vergeet je niet gewoon de parent constructor aan te roepen?

PHP:
1
parent::__constructor();

Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
Het gekke is dat de constructor wel wordt aangeroepen, maar dat er daarna niks meer gebeurt:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Boldable extends Mixin {

function __construct() { echo "b"; }
public function bold() { return "<b>" . $this->toString() . "</b>"; }

}

class Nixim extends Object {

function __construct() { echo "a"; }
protected $mixins = array("Boldable");
public function toString() { return "Nixim"; }

}

$n = new Nixim();
echo $n->bold();


geeft alleen een "a" op het scherm. Als ik de constructor in Nixim verwijder, dan zie ik dat de constructor van Boldable maar liefst zes keer wordt aangeroepen: "bbbbbbNixim".

Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
De constructor van Object word niet aangeroepen en de constructor van Boldable word een keer per method aangeroepen; iets wat sowieso efficienter kan maar wat ik destijds voor de voorbeeld code niet relevant vond.

Acties:
  • 0 Henk 'm!

  • xilent_xage
  • Registratie: Februari 2005
  • Laatst online: 15-09 11:35
got it. Bedankt allemaal!
Pagina: 1