Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PHP Simple Annotations, see http://marc.info/?t=146314168300003&r=1&w=2
<?php
/**
* The following stuff would be implemented in php-src (C).
*
* @author Richard Fussenegger <php@fleshgrinder.com>
* @license PHP-3.0
*/
/**
* Global annotation registry.
*
* @var array<bool, callable>
*/
$annotation_registry = [];
/**
* Check if an {@see Annotation} or {@see callable} has been registered for the given annotation name.
*/
function annotation_is_registered(string $name): bool {
global $annotation_registry;
return isset($annotation_registry[$name]);
}
/**
* Internal helper function to handle registration in a generic way.
*/
function __annotation_register(string $name, bool $singleton, callable $callback): bool {
if (annotation_is_registered($name)) {
return false;
}
global $annotation_registry;
$annotation_registry[$name] = [$singleton, $callback];
return true;
}
/**
* Register custom callback to handle given annotation.
*
* @see annotation_register_class()
*/
function annotation_register(string $name, callable $callback): bool {
return __annotation_register($name, false, $callback);
}
/**
* Register an {@see Annotation} class to handle given annotation.
*/
function annotation_register_class(string $name, string $class): bool {
$reflector = new ReflectionClass($class);
if ($reflector->implementsInterface(Annotation::class) === false) {
throw new Error("Class {$class} must implement the Annotation interface");
}
$constructor = $reflector->getConstructor();
foreach ($constructor->getParameters() as $parameter) {
if ($parameter->hasType()) {
$type = $parameter->getType();
// I am sure this is possible in C.
if ($type->isScalar() === false) {
throw new Error('Annotation constructors cannot type hint against non-scalar values.');
}
}
}
$callback = function (...$arguments) use ($class) {
return new $class(...$arguments);
};
if ($reflector->implementsInterface(AnnotationSingleton::class)) {
return annotation_register_singleton($name, $callback);
}
return annotation_register($name, $callback);
}
/**
* Register custom callback to handle given singleton annotation.
*/
function annotation_register_singleton(string $name, callable $callback): bool {
return __annotation_register($name, true, $callback);
}
/**
* Unregister the annotation with the given name.
*/
function annotation_unregister(string $name): bool {
global $annotation_registry;
if (annotation_is_registered($name)) {
unset($annotation_registry[$name]);
return true;
}
return false;
}
/**
* Classes implementing this interface can be registered, however, their constructor is limited to scalar values and the
* implementation of this interface results in PHP ensuring exactly that. Classes that violate this contract will result
* in a fatal error. Static code analyzers and IDEs can use the implementation of this interface to warn their users and
* display an error if the constructor type hints against anything else than scalar values.
*/
interface Annotation {
// Intentionally left blank.
}
/**
* An annotation singleton is an annotation that can only be applied once to a data structure.
*/
interface AnnotationSingleton extends Annotation {
// Intentionally left blank.
}
/**
* Collection of annotations of a single annotated data structure.
*/
final class Annotations implements Countable, IteratorAggregate {
private $argument_list;
private $name;
public function __construct(string $name, array $argument_list) {
$this->name = $name;
$this->argument_list = $argument_list;
}
public function count() {
return count($this->argument_list);
}
public function getIterator() {
global $annotation_registry;
list($singleton, $callback) = $annotation_registry[$this->name];
if ($singleton && count($this->argument_list) !== 1) {
throw new Error("Annotation {$this->name} is a singleton, but was applied more than once.");
}
foreach ($this->argument_list as $arguments) {
yield $callback(...$arguments);
}
}
}
interface ReflectorAnnotatable extends Reflector {
/**
* This method must be added to the following reflection classes:
*
* * {@see ReflectionClass}
* * {@see ReflectionFunctionAbstract} ({@see ReflectionFunction} and {@see ReflectionMethod})
* * {@see ReflectionProperty}
* * {@see ReflectionObject}
*
* @return array
*/
function getAnnotations(string $filter = null): array;
}
/**
* An annotated data structure is anything in PHP that can be annotated. It is used to read annotations from an
* annotated data structure, hence, it could as well be called annotation reader.
*/
abstract class AnnotatedDataStructure implements Countable, IteratorAggregate {
/** @var Annotations[] */
protected $annotation_list;
public function __construct(ReflectorAnnotatable $reflector) {
foreach ($reflector->getAnnotations() as $name => $argument_list) {
$this->annotation_list[$name] = new Annotations($name, $argument_list);
}
}
/**
* Count **all** annotations of this data structure.
*/
public function count(): int {
return count($this->annotation_list);
}
/**
* Get annotations by name.
*/
public function get(string $name): Annotations {
return $this->annotation_list[$name];
}
/**
* Get **all** annotations of this data structure.
*/
public function getIterator() {
foreach ($this->annotation_list as $annotations) {
yield $annotations;
}
}
/**
* Check if this data structure was annotated with given named annotation.
*/
public function has(string $name): bool {
return isset($this->annotation_list[$name]);
}
}
//
// **NOTE**
//
// I do not see any value in allowing annotations for parameters nor generators.
//
class AnnotatedClass extends AnnotatedDataStructure {
public function __construct($class) {
parent::__construct(new ReflectionClass($class));
}
}
class AnnotatedFunction extends AnnotatedDataStructure {
public function __construct($function) {
parent::__construct(new ReflectionFunction($function));
}
}
class AnnotatedMethod extends AnnotatedDataStructure {
public function __construct($class, $method_name) {
parent::__construct(new ReflectionMethod($class, $method_name));
}
}
class AnnotatedObject extends AnnotatedDataStructure {
public function __construct($object) {
parent::__construct(new ReflectionObject($object));
}
}
class AnnotatedProperty extends AnnotatedDataStructure {
public function __construct($class, $property_name) {
parent::__construct(new ReflectionProperty($class, $property_name));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment