Skip to content

Annotations


Changed in v6

The annotations component has been rewritten. In v5 and earlier, annotations were read from class, method, and property docblocks (for example @Column(type="integer")) by a dedicated parser written in C. In v6 the component reads native PHP 8 attributes (#[Column(type: 'integer')]) through the Reflection API. Docblock annotations are no longer supported. Any code or template that relied on the docblock syntax must be migrated to attributes.

Overview

The Phalcon\Annotations namespace reads PHP 8 attributes from classes, methods, properties, and class constants. It returns them as objects you can inspect at runtime.

Despite the historical name, the component does not parse anything. There is no lexer and no grammar. Phalcon\Annotations\Parser\Reader calls ReflectionClass::getAttributes() and wraps each ReflectionAttribute in a Phalcon\Annotations\Parser\Annotation object. It is an attributes reader.

The component is used internally by two framework features:

  • The model metadata strategy reads model-mapping attributes (Column, Primary, Identity).
  • The annotations router reads routing attributes (Route, RoutePrefix, and the HTTP verb attributes).

You can also read your own attributes with the same API.

Reading Attributes

Phalcon\Annotations\Annotations is the entry point. Its constructor requires a cache adapter that implements Phalcon\Annotations\Adapter\AdapterInterface. The adapter stores the reflected results so a class is only reflected once.

<?php

use MyApp\Models\Invoices;
use Phalcon\Annotations\Adapter\Memory;
use Phalcon\Annotations\Annotations;
use Phalcon\Storage\SerializerFactory;

$serializerFactory = new SerializerFactory();
$adapter           = new Memory($serializerFactory);

$annotations = new Annotations($adapter);

$reflection = $annotations->get(Invoices::class);

get() accepts a class name or an object and returns a Phalcon\Annotations\Parser\Reflection. From the reflection you read the attributes grouped by location:

$classAttributes      = $reflection->getClassAnnotations();      // Collection | null
$methodAttributes     = $reflection->getMethodsAnnotations();    // array of Collection, keyed by method
$propertyAttributes   = $reflection->getPropertiesAnnotations(); // array of Collection, keyed by property
$constantAttributes   = $reflection->getConstantsAnnotations();  // array of Collection, keyed by constant

For a single method, property, or constant, the component offers direct accessors that return a Phalcon\Annotations\Parser\Collection:

$methodAttributes   = $annotations->getMethod(Invoices::class, 'viewAction');
$propertyAttributes = $annotations->getProperty(Invoices::class, 'id');
$constantAttributes = $annotations->getConstant(Invoices::class, 'STATUS_PAID');

Collection

A Collection holds the attributes found at one location. Attribute names are stored as the short class name, so #[Phalcon\Annotations\Models\MetaData\Column] is looked up as Column.

$propertyAttributes = $annotations->getProperty(Invoices::class, 'id');

if ($propertyAttributes->has('Column')) {
    $column = $propertyAttributes->get('Column'); // Annotation

    echo $column->getNamedArgument('type');
}

foreach ($propertyAttributes as $attribute) {
    echo $attribute->getName();
}

get() throws a Phalcon\Annotations\Parser\Exception when the requested name is not present. Call has() first, or use getAll() to retrieve every attribute of a given name.

Annotation

Each entry in a collection is a Phalcon\Annotations\Parser\Annotation. It exposes the attribute name and the arguments it was constructed with.

Method Returns
getName() The short attribute name (for example Column)
getArguments() All arguments as an array
getArgument($position) A single argument by integer position or string name
getNamedArgument($name) A single argument by name
hasArgument($position) Whether an argument exists at that position or name
numberArguments() The number of arguments
$methodAttributes = $annotations->getMethod(Invoices::class, 'viewAction');
$route            = $methodAttributes->get('Get');

echo $route->getArgument(0);            // the route pattern, passed positionally
echo $route->getNamedArgument('name');  // the route name, passed by name

Positional arguments are keyed by integer (getArgument(0)); named arguments are keyed by name (getNamedArgument('name')). This mirrors how the attribute was written in the source.

Defining Attributes

An attribute is a plain PHP class marked with the #[Attribute] attribute. Declare the targets it applies to, and declare its arguments as constructor parameters.

<?php

namespace MyApp\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Cacheable
{
    public function __construct(
        public int $lifetime = 3600
    ) {
    }
}

Apply it, then read it back with the component:

<?php

namespace MyApp\Controllers;

use MyApp\Attributes\Cacheable;
use Phalcon\Mvc\Controller;

class ReportsController extends Controller
{
    #[Cacheable(lifetime: 86400)]
    public function yearlyAction(): void
    {
    }
}
$attributes = $annotations->getMethod(ReportsController::class, 'yearlyAction');

if ($attributes->has('Cacheable')) {
    $lifetime = $attributes->get('Cacheable')->getNamedArgument('lifetime');
}

Model Metadata Attributes

Phalcon ships attributes that describe how a model maps to a database table. They live in the Phalcon\Annotations\Models\MetaData namespace.

Attribute Target Arguments
Column property column, type, length, nullable, skipOnInsert, skipOnUpdate, allowEmptyString, default
Primary property none
Identity property none

The model metadata strategy reads Column, Primary, and Identity to build the table column map. The model's table name is set with the getSource() method, shown below.

<?php

namespace MyApp\Models;

use Phalcon\Annotations\Models\MetaData\Column;
use Phalcon\Annotations\Models\MetaData\Identity;
use Phalcon\Annotations\Models\MetaData\Primary;
use Phalcon\Mvc\Model;

class Invoices extends Model
{
    #[Primary]
    #[Identity]
    #[Column(column: 'inv_id', type: 'integer', nullable: false)]
    public int $id;

    #[Column(column: 'inv_title', type: 'string', nullable: false)]
    public string $title;

    #[Column(column: 'inv_total', type: 'decimal', nullable: false)]
    public float $total;

    public function getSource(): string
    {
        return 'co_invoices';
    }
}

The column argument sets the database column name. The type argument maps to a Phalcon\Db\Column type. To make a model use these attributes, configure its metadata component with the annotations strategy. See Models Metadata for details.

Routing Attributes

Phalcon ships attributes that declare routes on a controller. They live in the Phalcon\Annotations\Router namespace and are read by Phalcon\Mvc\Router\Annotations.

Attribute Target Description
RoutePrefix class Prefixes every route in the controller. Argument: prefix
Route method Declares a route. Arguments: route, methods, name, paths, converters
Get method A Route restricted to the GET method
Post method A Route restricted to the POST method
Put method A Route restricted to the PUT method
Patch method A Route restricted to the PATCH method
Delete method A Route restricted to the DELETE method
Head method A Route restricted to the HEAD method
Options method A Route restricted to the OPTIONS method
Connect method A Route restricted to the CONNECT method
Purge method A Route restricted to the PURGE method
Trace method A Route restricted to the TRACE method

The verb attributes are thin subclasses of Route that preset the HTTP method. Use Route directly when a route answers more than one method.

<?php

namespace MyApp\Controllers;

use Phalcon\Annotations\Router\Get;
use Phalcon\Annotations\Router\Post;
use Phalcon\Annotations\Router\RoutePrefix;
use Phalcon\Mvc\Controller;

#[RoutePrefix('/invoices')]
class InvoicesController extends Controller
{
    #[Get('/')]
    public function indexAction(): void
    {
    }

    #[Get('/view/{id:[0-9]+}', name: 'invoices-view')]
    public function viewAction(int $id): void
    {
    }

    #[Post('/')]
    public function createAction(): void
    {
    }
}

For registering the controllers with the annotations router, see Routing.

Adapters

The adapter passed to Phalcon\Annotations\Annotations caches the reflected attributes. The following adapters are available in the Phalcon\Annotations\Adapter namespace. Each extends the matching Phalcon\Storage\Adapter class, so its constructor accepts a Phalcon\Storage\SerializerFactory and an options array.

Adapter Description
Apcu Stores the cache in APCu. Suitable for production.
Libmemcached Stores the cache in Memcached.
Memory Stores the cache in memory. Rebuilt per request. Suitable for development.
Redis Stores the cache in Redis.
Stream Stores the cache in files on disk.
Weak Stores the cache using weak references.

Construct an adapter directly:

<?php

use Phalcon\Annotations\Adapter\Stream;
use Phalcon\Storage\SerializerFactory;

$serializerFactory = new SerializerFactory();

$adapter = new Stream(
    $serializerFactory,
    [
        'storageDir' => '/app/storage/cache/annotations',
    ]
);

Or create one through Phalcon\Annotations\AdapterFactory, which resolves the adapter by name:

<?php

use Phalcon\Annotations\AdapterFactory;
use Phalcon\Annotations\Annotations;
use Phalcon\Storage\SerializerFactory;

$serializerFactory = new SerializerFactory();
$adapterFactory    = new AdapterFactory($serializerFactory);

$adapter     = $adapterFactory->newInstance('apcu', ['lifetime' => 86400]);
$annotations = new Annotations($adapter);

The factory accepts the names apcu, libmemcached, memory, redis, stream, and weak.

When you register the component in the DI container under the name annotations, both the model metadata strategy and the annotations router resolve it from there.

<?php

use Phalcon\Annotations\Adapter\Apcu;
use Phalcon\Annotations\Annotations;
use Phalcon\Di\FactoryDefault;
use Phalcon\Storage\SerializerFactory;

$container = new FactoryDefault();

$container->set(
    'annotations',
    function () {
        $serializerFactory = new SerializerFactory();
        $adapter           = new Apcu($serializerFactory, ['lifetime' => 86400]);

        return new Annotations($adapter);
    }
);

Exceptions

Exceptions thrown by this component are of type Phalcon\Annotations\Parser\Exception, which extends the SPL \Exception. The most common case is calling Collection::get() with a name that does not exist.

<?php

use MyApp\Models\Invoices;
use Phalcon\Annotations\Adapter\Memory;
use Phalcon\Annotations\Annotations;
use Phalcon\Annotations\Parser\Exception;
use Phalcon\Storage\SerializerFactory;

$serializerFactory = new SerializerFactory();
$annotations       = new Annotations(new Memory($serializerFactory));

try {
    $attributes = $annotations->getProperty(Invoices::class, 'id');
    $column     = $attributes->get('Column');
} catch (Exception $ex) {
    echo $ex->getMessage();
}