How Is Annotation Useful in PHP

How is annotation useful in PHP?

Rob Olmos explained it right:

Annotations basically let you inject behavior and can promote decoupling.

In my words I'd say that these annotations are valuable especially in context of reflection where you gather (additional) metadata about the class/method/property you are inspecting.

Another example instead of ORM: Dependency Injection frameworks. The upcoming FLOW3 framework for example uses docComments/annotations to identify which objects are injected in an instance created from a DI container instead of specifying it in an XML configuration file.

Oversimplified example following:

You have two classes, one Soldier class and a Weapon class. A Weapon instance gets injected in a Soldier instance. Look at the definition of the two classes:

class Weapon {
public function shoot() {
print "... shooting ...";
}
}

class Soldier {
private $weapon;

public function setWeapon($weapon) {
$this->weapon = $weapon;
}

public function fight() {
$this->weapon->shoot();
}
}

If you would use this class and inject all dependencies by hand, you´d do it like this:

$weapon = new Weapon();

$soldier = new Soldier();
$soldier->setWeapon($weapon);
$soldier->fight();

All right, that was a lot of boilerplate code (bear with me, I am coming to explain what annotations are useful for pretty soon). What Dependency Injection frameworks can do for you is to abstract the creation such composed objects and inject all dependencies automatically, you simply do:

$soldier = Container::getInstance('Soldier');
$soldier->fight(); // ! weapon is already injected

Right, but the Container has to know which dependencies a Soldier class has. So, most of the common frameworks use XML as configuration format. Example configuration:

<class name="Soldier">
<!-- call setWeapon, inject new Weapon instance -->
<call method="setWeapon">
<argument name="Weapon" />
</call>
</class>

But what FLOW3 uses instead of XML is annotations directly in the PHP code in order to define these dependencies. In FLOW3, your Soldier class would look like this (syntax only as an example):

class Soldier {
...

// ---> this

/**
* @inject $weapon Weapon
*/
public function setWeapon($weapon) {
$this->weapon = $weapon;
}

...

So, no XML required to mark the dependency of Soldier to Weapon for the DI container.

FLOW 3 uses these annotations also in the context of AOP, to mark methods which should be "weaved" (means injecting behaviour before or after a method).


As far as I am concerned, I am not too sure about the usefulness about these annotations. I dont know if it makes things easier or worse "hiding" this kind of dependencies and setup in PHP code instead of using a separate file.

I worked e. g. in Spring.NET, NHibernate and with a DI framework (not FLOW3) in PHP both based on XML configuration files and cant say it was too difficult. Maintaining these setup files was ok, too.

But maybe a future project with FLOW3 proves the opposite and annotations are the real way to go.

How to use PHP to annotate an string with HTML (i.e How. insert HTML tags to an string by offsets mantaining a valid HTML)?

After loading the HTML into a DOM document, you can fetch any text node descendant of an element node with an Xpath expression (.//text()) in an iterable list. This allows you to keep track of the characters before the current text node. On the text node you check if the text content (or a part of it) has to be wrapped into the annotation tag. If so separate it and create a fragment with up to 3 nodes. (text before, annotation, text after). Replace the text node with the fragment.

function annotate(
\DOMElement $container, int $start, int $end, string $name
) {
$document = $container->ownerDocument;
$xpath = new DOMXpath($document);
$currentOffset = 0;
// fetch and iterate all text node descendants
$textNodes = $xpath->evaluate('.//text()', $container);
foreach ($textNodes as $textNode) {
$text = $textNode->textContent;
$nodeLength = grapheme_strlen($text);
$nextOffset = $currentOffset + $nodeLength;
if ($currentOffset > $end) {
// after annotation: break
break;
}
if ($start >= $nextOffset) {
// before annotation: continue
$currentOffset = $nextOffset;
continue;
}
// make string offsets relative to node start
$relativeStart = $start - $currentOffset;
$relativeLength = $end - $start;
if ($relativeStart < 0) {
$relativeLength -= $relativeStart;
$relativeStart = 0;
}
$relativeEnd = $relativeStart + $relativeLength;
// create a fragment for the annotation nodes
$fragment = $document->createDocumentFragment();
if ($relativeStart > 0) {
// append string before annotation as text node
$fragment->appendChild(
$document->createTextNode(grapheme_substr($text, 0, $relativeStart))
);
}
// create annotation node, configure and append
$span = $document->createElement('span');
$span->setAttribute('class', 'annotation '.$name);
$span->textContent = grapheme_substr($text, $relativeStart, $relativeLength);
$fragment->appendChild($span);
if ($relativeEnd < $nodeLength) {
// append string after annotation as text node
$fragment->appendChild(
$document->createTextNode(grapheme_substr($text, $relativeEnd))
);
}
// replace current text node with new fragment
$textNode->parentNode->replaceChild($fragment, $textNode);
$currentOffset = $nextOffset;
}
}

$html = <<<'HTML'
<div><div>This is</div> only a test for stackoverflow</div>
HTML;

$annotations = [
0 => [0, 3],
1 => [2, 6],
2 => [8, 10]
];

$document = new DOMDocument();
$document->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

foreach ($annotations as $index => $offsets) {
annotate($document->documentElement, $offsets[0], $offsets[1], 'n-'.$index);
}

echo $document->saveHTML();

Output:

<div><div><span class="annotation n-0">Th<span class="annotation n-1">i</span></span><span class="annotation n-1">s is</span></div> <span class="annotation n-2">on</span>ly a test for stackoverflow</div>

The mechanism of annotations in Symfony - how does it all work?

Yes, indeed, annotations are not part of the language itself. But they're also not the part of Symfony framework.

Annotations are usually handled by doctrine/annotations package (most common). It utilizes reflection to read and parse these comments and transform them into annotation objects (every annotation has an annotation class which it represents).
Then, its up to the library to make use of generated objects representing these annotations.

So to answer first question - yes, there is a preprocessor. But it doesn't "create new php entities", because its the job for the library that uses those annotations (e.g. Symfony framework or Doctrine ORM).

How it affects the performance, depends on the library that uses them. If they would be parsed on every request, that would indeed affect performance. So e.g. Symfony and Doctrine ORM cache this data or create proxy classes etc.

So the answer to second question is - it might if used incorrectly, but it usually is not (in production environment) as they are simply not parsed every time.

The last question doesn't really relates to annotations. Since annotations are really classes, the reason for namespacing them is also the same. To avoid conflicts between libraries and for sake of readability.

PHP Type Annotation for functions?

That would just be:

/**
* @return string
*/
private function getName(): string {}

The annotation is optional at this point.

What does the annotation @template mean in PHP doc blocks?

The @template annotation relates to a concept called Generics, which does not currently exist in PHP, but are a way of dynamically describing the contents of a parameter or return type that would be unknown until a class is instantiated or method called.

For PHP specifically, here is an article describing the doc blocks themselves and how to use them.

For the code you referenced, the template specifies T will be an instance of Extension\Extension. The $id parameter will be the class name for T, and @return says the method will return an instance of T.

Using the method would be something like $faker->ext(MyExtension::class), which would return an instance of MyExtension.

If you want to follow the rabbit, here is more on Generic Programming.

Specifying return value in annotation in PHP

There is an issue on github, describing this problem.
issue

Some code using that feature:

/**
* @return null|array{
* md5: ?string,
* data?: array,
* size?: ?string,
* bit_rate?: ?string,
* remote?: ?string("remote", "local"), // documents possible return values
* url?: ?string,
* body?: ?string,
* refresh?: bool,
* refreshRate?: int,
* data?: array{
* playlist?: array,
* repeat?: array,
* width?: int,
* height?: int,
* compression?: mixed,
* depth?: int,
* alpha_channel?: int,
* orientation?: string,
* url?: string,
* }
* }
*/
function getInfo(): ?array {
return [
'md5' => md5('some info'),
'url' => 'https://example.com/something',
'data' => [],
'remote' => 'remote' // possible values `remote` or `local`
];
}

I think the best option is to document array in the function description:

/**
* Returns information about audio source
*
* Returns
* array['md5'] null|string md5 hash of something
* array['size']? null|string size of audio (should be numeric)
* array['bit_rate']? null|string bit rate
* array['remote']? null|string is it a remote source possible values: "remote", "local" (should be bool)
* array['url']? null|string remote url
* array['body'] null|string stream body
* array['refresh']? bool some option
* array['refreshRate'] int refresh rate
* array['data']? array audio data
* ['playlist']? array some playlist data
* ['repeat']? array some repeat data
* ['width']? int width of something
* ['height']? int height of something
* ['compression']? mixed some compression data
* ['delth']? int depth value
* ['alpha_channel']? int alpha channel value
* ['orientation']? string some orientation data
* ['url']? string url of something
*
* @return null|array see above
*/

How to annotate PHP function/method which never returns control by design for PhpStorm

This still needs an answer (for the Docblock annotation), it is:

/**
* @return no-return
*/

Support in PHPStorm since 2020.3 (3 Dec 2020, before in EAP 2, see WI-55898).


The more canonical answer is:

/**
* @return never
*/

The answer is more canonical in the sense that never [DOC] [RFC] is the language keyword from PHP 8.1 onwards for its bottom type [WP] - return type of never returning functions.

  • Compare: What is never return type in PHP 8.1

But as of today the support in PHPStorm is limited for this variant. You find it in effect to what you ask for (detects unreachable statements), but there are still false positives that mark the PHPDoc comment to not match the actual return type.

Humble we goes with the first answer and canonical when the tooling allows us. Other tools are already fine with, maybe next Phpstorm release, too.


The general answer is any of these:

/**
* @return never
* @return never-return
* @return never-returns
* @return no-return
*/

The reason why so many alternatives? One educated guess is that now as never went into PHP 8.1 on the initiative by Matt Brown and Ondřej Mirtes (authors of PHP static analyzer tools Psalm and Phpstan) and both projects did already streamline the annotations in the past this is how the list turned out (if you really want to catch 'em all, add noreturn to the list as it was previously suggested per the RFC but didn't make it, the winner is never and takes it all).

Humble we for ourselves found @return no-return working in Phpstorm while having already worked with @psalm-return no-return flawlessly before. We can imagine same is true for Phpstan.

Now as one (or three half) answers have been given, what follows is this answers'



PHP Know-Your-Language Quiz


Phpstorm (and other static analysers like Psalm or PHPStan) can take a shortcut by implying that no-return is not a valid PHP class name.

This should normally be perfectly fine on the level of static code analysis as you could not put

class no-return 
{
}

into PHP source code, right? (It's a syntax error if not tableflip.) So no-one would use such class names anyway.

But a question remains: Is no-return a valid class name? One you can instantiate an object from, call methods on etc. in PHP?

On the PHP language level there is a caveat: no-return is a perfectly valid PHP class name since PHP 5.3 when the class_alias() function has been introduced to provide libraries with the option to switch to namespaces, but to keep backward compatibility.

Since then such class names are allowed in PHP - but only for the alias in user land. If you go deeper, "classes [with] such names could - theoretically - exist due to weird (3rd party) extensions etc.". [REF]

annotating a local variable in php

The Phpdoc standard does not cover these annotations (it only cover class properties with the @var tag); however, it is perfectly possible in Eclipse (e.g. PDT):

/* @var $variable Type */
^ ^ `--- type
| variable
|
`--- single star

This also works in all other PHP IDEs like Netbeans or Phpstorm which is useful if you exchange your code with others.

Example Code:

<?php

/* @var $doc DOMDocument */
$doc->
 

Example Screenshot (Eclipse PDT (Indigo)):

Eclipse PDT (Indigo)

Related Question & Answers:

  • How do I make my PHP IDE understand Dependency Injection Containers?
  • Is there a way to make PhpStorm's autocomplete “go deeper”?


Related Topics



Leave a reply



Submit