[howto] Annotations in PHP

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

Acties:
  • 0 Henk 'm!

  • eamelink
  • Registratie: Juni 2001
  • Niet online

eamelink

Droptikkels

Topicstarter
Annotations?
Annotations zijn stukjes extra informatie in je sourcecode die niet direct uitgevoerd worden maar als metadata aanwezig zijn. Een stukje commentaar is bijvoorbeeld een annotation; metadata die een beschrijving geeft van bijvoorbeeld een methode.

In een aantal imperatieve talen kunnen annotations ook gebruikt worden om functionaliteit toe te voegen aan een programma. De annotations kunnen worden uitgelezen door het programma dat ze kan gebruiken. Zo kan je stiekum declaratief programmeren in een imperatieve taal en dat is natuurlijk tof >:)

Annotations in PHP
PHP heeft zelf geen native support voor annotations, maar er zit al wel een tijd lang een reflectie API in. Dat betekent dat je vanuit je script de structuur van je script kunt analyseren. Eén van de dingen die je ermee kan ophalen is de docblock van een gedocumenteerd element :*). Die kan vervolgens verder uitgeplozen worden om er nuttig spul uit te halen en zo heb je annotations gefaked :)

Das vast retetraag!
Dat vermoedde ik ook, maar een korte benchmark liet zien dat op mijn pc (Dothan 1.6Ghz), het ophalen van een docblock entry van een methode in een class van zo'n 30 methoden én het verwerken van de elementen in die docblock naar een array in 0.15 msec gedaan werd. Dat moet vlot genoeg zijn voor normaal gebruik :)

Maar het is wel ranzig!
Vind je? Vind ik ook wel een beetje, maar het is wel sexy O+

Voorbeeldje?
Stel je hebt een framework gemaakt waarbij sommige methoden ook aangeroepen moeten kunnen worden met een ajax request, dan kan je bijvoorbeeld zoiets doen:

PHP:
1
2
3
4
5
6
7
/**
* @remoteCallable true
*/
function getUser($id){
  // do things
  return $user;
}


Of wat dacht je van security:
PHP:
1
2
3
4
5
6
7
/**
 * @object user_admin
 * @action edit
 */
 function updateUser($data){
    //bla
  }

Zo kan je methodes beveiligen met behulp van metadata; je framework kan vóórdat de methode aangeroepen wordt al kijken of de gebruiker wel de rechten 'edit' op het object 'user_admin' heeft :)

Of bijvoorbeeld voor validatieregels:
PHP:
1
2
3
4
5
6
7
class User {
  /**
   * @validate /[a-z0-9-_]{6-20}/
   * @unique
   */
   public $username;
}


En hoe gebruik ik dat?
Je zou zoiets kunnen gebruiken om van een bepaalde methode de annotations te vinden.
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
function getAnnotations($class, $method){
    $rmethod = new ReflectionMethod($class, $method);
    $comment = $rmethod->getDocComment();
    preg_match_all('/@([a-z]+)\s(.*)/', $comment, $matches);
    $annotations = array();
    for($i = 0; $i < count($matches[0]); $i++){
        $key = $matches[1][$i];
        $value = $matches[2][$i];
        $annotations[$key] = $value;
    }
    return $annotations;
}


Zo krijg je de annotations van een methode netjes in een associatieve array terug :)

Acties:
  • 0 Henk 'm!

  • apokalypse
  • Registratie: Augustus 2004
  • Laatst online: 16-09 21:55
Leuk, maar is dit niet helemaal de taal hiervoor? In PHP is wel meer ranzig ;) (hangt van je definitie af)

Acties:
  • 0 Henk 'm!

  • AaroN
  • Registratie: Februari 2001
  • Laatst online: 16-08-2023

AaroN

JayGTeam (213177)

Dit heeft wel wat weg van een implementatie van Aspect Oriented Programming. Er schijnt ook een PHP AOP library te zijn. Een oplossing voor het zelfde probleem :)

AspectJ heb je voor Java.

JayGTeam (213177)


Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

:o

Ik heb er nooit bij stilgestaan dat dit soort dingen mogelijk was in PHP. Het is inderdaad misschien ranzig maar het is inderdaad "sexy" (your quote :P). Ik ga me hier ook eens in verdiepen. :)
offtopic:
Is dit trouwens ook waar je me gisteravond voor aansprak? :P

[ Voor 14% gewijzigd door NMe op 06-12-2007 11:26 ]

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • TheDane
  • Registratie: Oktober 2000
  • Laatst online: 08:48

TheDane

1.618

eamelink schreef op woensdag 05 december 2007 @ 21:30:

En hoe gebruik ik dat?
Je zou zoiets kunnen gebruiken om van een bepaalde methode de annotations te vinden.
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
function getAnnotations($class, $method){
    $rmethod = new ReflectionMethod($class, $method);
    $comment = $rmethod->getDocComment();
    preg_match_all('/@([a-z]+)\s(.*)/', $comment, $matches);
    $annotations = array();
    for($i = 0; $i < count($matches[0]); $i++){
        $key = $matches[1][$i];
        $value = $matches[2][$i];
        $annotations[$key] = $value;
    }
    return $annotations;
}


Zo krijg je de annotations van een methode netjes in een associatieve array terug :)
Dat werkt nog aardig ook :D

Alleen hij pakt hierbij wel alleen de laatste value per key:
PHP:
1
2
3
4
5
6
    /**
     * @param string $result
     * @param integer $briefingID
     * @param integer $presetID
     * @return void
     */

code:
1
array(2) { ["param"]=>  string(17) "integer $presetID" ["return"]=>  string(4) "void" }


PHP:
1
$annotations[$key] = $value;

vervangen door
PHP:
1
$annotations[$key][] = $value;

=
code:
1
array(2) { ["param"]=>  array(3) { [0]=>  string(14) "string $result" [1]=>  string(19) "integer $briefingID" [2]=>  string(17) "integer $presetID" } ["return"]=>  array(1) { [0]=>  string(4) "void" } }


Dan heb je mooi alles. Voor eamelink's ranzige misbruik :9~ van Annotations misschien niet relevant, maar er zijn natuurlijk veel meer mogelijkheden :)

Gaaf! _/-\o_

Acties:
  • 0 Henk 'm!

  • YopY
  • Registratie: September 2003
  • Laatst online: 13-07 01:14
Annotations gebruik ik zelf vaak in Java tegenwoordig. Ben nu bezig met tests schrijven voor een app van ~160 classes waarbij ik gebruik maak van JUnit.

Een eerdere versie van JUnit had een paar dingen nodig om tests uit te kunnen voeren:

* Alle unit tests moesten extends zijn van TestCase.
* Tests in een unit test class (methoden) moesten met 'test' beginnen, zoals testPietKlaasInteractie().
* Het geheel maakte gebruik van reflection om classes en methodes te vinden die aan die voorwaarden voldeden. En reflection is viez.

De nieuwe(re) versies van JUnit lossen dit op met annotations. Je maakt een class aan zoals elke andere (evt met 'test' voor duidelijkheid tegenover de programmeur(s), maar JUnit verplicht het niet meer), en je maakt vervolgens gebruik van de @Test annotatie voor de methods die een test voorstellen om die door JUnit uit te laten voeren.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
public class HenkJanKlaasenTest {

  @Test
  public void pietjePuk() {
     assertTrue(!!!!!!!!!!!!!!false);
  }

  public boolean hulpMethode() {
     return !false;
  }

}


Dat vind ik veel handiger dan de 'ouwe' methode.

iig, @howto zelf, ik vind het gebruik van annotations voor AOP-achtige functionaliteit lichtelijk ranzig. Een goeie class is zelf niet afhankelijk of zelfs maar bewust van eventuele extra functionaliteit zoals logincontrole. Validatie van invoerwaarden is dan nog wel handig, maar AOP-oplossingen zouden mijns insziens toch op een andere manier opgelost moeten worden (mbv proxy objecten die aan de hand van configuratiebestanden tussen een aanroepende en een uitvoerende klasse zitten. Maar daarvoor moet je voor alle AOP-gebruikende klassen een interface maken, en daarnaast moet je óf via reflection een object in een bestaande klasse vervangen met een proxy klasse, óf via een Inversion of Control principe werken.)

...Heb ik het al duidelijk gemaakt dat ik een Java-sled ben?

Acties:
  • 0 Henk 'm!

  • djc
  • Registratie: December 2001
  • Laatst online: 08-09 23:18

djc

Leuk bedacht! Misschien kan ik er wel iets mee; even over nadenken.

Lijkt ook redelijk veel op Python decorators.

Rustacean


Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
-NMe- schreef op donderdag 06 december 2007 @ 11:25:
:o

Ik heb er nooit bij stilgestaan dat dit soort dingen mogelijk was in PHP.
Er is nog véél meer mogelijk in PHP :), zeker voor m'n view logica wil ik nog wel eens een flinke trukendoos open trekken om het een en ander eenvoudiger (of minder repetitief) te maken. De volgende code gebruik ik bijvoorbeeld om m'n PHP syntax iets minder verbose te maken.

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
//! Simple class that alows me to read the templates as a special stream (view://).
//
// Advantages are: 
//
// - Not having to worry about short_open_tags as we handle that manually.
// - Shorter syntax for accessing variables (@$some_variable for $this->some_variable).
// - Shorter syntax for accessing methods (@->some_method() for $this->some_method()).
// - Shorter syntax for embedding chunks ~('name.of.chunk') in a PHP block.
// - Shorter syntax for escaping variables \($some_data) in a PHP block.
//
// Usage:
//
// <code>
// require_once('view://./templates/some.template.tpl.php');
// </code>
vpViewStream::register();
class vpViewStream{
    private $path;
    private $length     = 0;
    private $position   = 0;
    private $stat       = null;
    private $data;
    
    public static function register(){
        stream_wrapper_register('view', __CLASS__);
    }

    public function stream_open($path, $mode, $options, $opened_path){
        $path           = str_replace('view://', '', $path);
        $this->data     = file_get_contents($path);
        $this->stat     = stat($path);
        
        if($this->data === false)
            return false;

        if(!@ini_get('short_open_tags')){
            $this->data = str_replace(array('<?=', '<? '), array('<?php echo ', '<?php '), $this->data);
        }
            
        $this->data     = str_replace(array('@$', '@->'), '$this->', $this->data);
        // nu ik er over na denk, zou deze regel wel héél mooi gebruik kunnen maken van Annotations...
        $this->data     = str_replace(array('~(', '\('), array('$this->p_chunk(', '$this->escaped('), $this->data);
        $this->path     = $path;
        $this->length   = strlen($this->data);
        
        return true;
    }
    
    public function stream_seek($offset, $whence){
        switch($whence){
            case SEEK_SET:
                if($offset < $this->length && $offset >= 0){
                    $this->position = $offset;
                    return true;
                }
                break;
            case SEEK_CUR:
                if($offset >= 0){
                    $this->position += $offset;
                    return true;
                }
                break;
            case SEEK_END:
                if($this->length + $offset >= 0){
                    $this->position = $this->length + $offset;
                    return true;
                }
                break;
        }
        
        return false;
    }
    
    public function stream_read($count){
        $str = substr($this->data, $this->position, $count);
        $this->position += strlen($str);
        return $str;
    }
    
    public function stream_tell(){
        return $this->position;
    }
    
    public function stream_stat(){
        return $this->stat;
    }
    
    public function stream_eof(){
        return $this->position > $this->length;
    }
}


Templates (die normaal in PHP geschreven werden) zien er nu ongeveer zo uit:
PHP:
1
2
3
4
5
6
7
8
9
10
11
<?php @->p_block_begin(\(@$list->get_title()))?>

<p>
    <?php @->p_ubb(@$list->get_description())?>
</p>

<?~('top.40', array("title" => \("Top 40"), "top_list" => @$top_list))?>
<?~('top.40', array("title" => \("Nominaties"), "top_list" => @$top_recommended))?>

<a href="<?=@->base_url()?>?votable_create&top_list_id=<?php echo @$list->get_id() ?>">Nomineer!</a>
<?php @->p_block_end() ?>


En worden zo gebruikt:
PHP:
1
require_once('view://path.to.template.php');


Verder heb ik ook nog classes in gebruik die het gedrag van Ruby's mixins proberen te imiteren (wat ook erg fijn werkt voor je Views).

Acties:
  • 0 Henk 'm!

Verwijderd

PrisonerOfPain schreef op zondag 09 december 2007 @ 04:35:
[...]


Er is nog véél meer mogelijk in PHP :), zeker voor m'n view logica wil ik nog wel eens een flinke trukendoos open trekken om het een en ander eenvoudiger (of minder repetitief) te maken. De volgende code gebruik ik bijvoorbeeld om m'n PHP syntax iets minder verbose te maken.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
//! Simple class that alows me to read the templates as a special stream (view://).
//
// Advantages are: 
//
// - Not having to worry about short_open_tags as we handle that manually.
// - Shorter syntax for accessing variables (@$some_variable for $this->some_variable).
// - Shorter syntax for accessing methods (@->some_method() for $this->some_method()).
// - Shorter syntax for embedding chunks ~('name.of.chunk') in a PHP block.
// - Shorter syntax for escaping variables \($some_data) in a PHP block.
//
// Usage:
//


...

Verder heb ik ook nog classes in gebruik die het gedrag van Ruby's mixins proberen te imiteren (wat ook erg fijn werkt voor je Views).
Damn wat is dit ongelooflijk ranzig in mijn ogen. Voor het beschikbaar maken van geregistreerde variabelen gebruiken wij gewoon onderstaande code:

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
class PhpView
{

    // ...

    public function render (Request $request)
    {
        // Make attributes accessible in the template
        foreach ($request->getAttributes () as $key => $value)
            $$key = $value;

        // Make the error messages accesible in the template
        $errorMessages = $request->getErrorMessages ();

        // Make the User object accessible in the template
        $user = $this->getContext()->getUser ();

        // render to variable
        ob_start();

        require($this->getTemplate());

        $content = ob_get_contents();

        ob_end_clean();

        $request->setAttribute ('content', $content);                
        
        // Look if we need to decorate the output
        if ($this->isDecorated ()) {
            $controller = $this->getContext()->getController();
            $decorator = $controller->getDecorator ($this->decorator);
            $decorator->execute ($request);
        
            // Return the decorated output          
            return $decorator->render ($request);
        } else {
            // Return the undecorated output 
            return $content;
        }
    }

    // ...
}


Om even op de annotations terug te komen; het lijkt me voor een paar doeleinden geschikt voor ontwikkelings fase, maar zeker niet voor productie. Wel zie ik mogelijkheden om een compiler te schrijven die geannoteerde fragmenten omzet naar php die precies hetzelfde doet.

Acties:
  • 0 Henk 'm!

  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 26-05 17:08
Verwijderd schreef op zondag 09 december 2007 @ 14:23:
[...]


Damn wat is dit ongelooflijk ranzig in mijn ogen. Voor het beschikbaar maken van geregistreerde variabelen gebruiken wij gewoon onderstaande code:
Het enige ranzige er aan is, in mijn ogen, dat vpViewStream af weet van de methoden die vpViewBase heeft (namelijk p_chunk en escaped); het is ook de bedoeling dat daar een op reflection gebaseerde oplossing voor komt. Jammer genoeg noem je ook geen reden voor de ranzigheid. Verder levert het view:// 'protocol' een mooie en transparante abstractie van de onderliggende template architectuur.

Acties:
  • 0 Henk 'm!

  • eghie
  • Registratie: Februari 2002
  • Niet online

eghie

Spoken words!

Ik vind dat het redelijk nuttig kan zijn. Vooral die remoteCallable icm een AJAX-to-PHP framework. Scheelt een hoop gezeik in de code structuur, om je methode wel of geen toegang daartoe te geven. En je hebt meteen de boel gedocumenteerd volgens de javadoc structuur. Als je zo de code documenteert kun je heel makkelijk een AJAX api voor externe partijen beschikbaar stellen. Gewoon voor alle remoteCallable de documentatie beschikbaar stellen.

Wat PHP ook nog heeft, als module: RunKit. Daar kun je ook wel leuke dingetjes mee. Daarmee kun je bijvoorbeeld PHP code in een sandbox draaien. Volgens mij is runkit nog wel een beetje expirimenteel.
Pagina: 1