Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Last active October 4, 2018 02:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mindplay-dk/ebd5e4f7da51da3c4e56232adef41b46 to your computer and use it in GitHub Desktop.
Save mindplay-dk/ebd5e4f7da51da3c4e56232adef41b46 to your computer and use it in GitHub Desktop.
annotations
<?php
class Route
{
public $pattern;
public $method;
public function __construct($pattern, $method)
{
$this->pattern = $pattern;
$this->method = $method;
}
public static function get($pattern) {
return new self($pattern, "GET");
}
}
class Filter
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
class UserController
{
<< Route::get('^/users/\w+$') >>
<< new Filter("membership") >>
public function showProfile()
{
// ...
}
}
$method = new ReflectionMethod(UserController::class, "showProfile");
var_dump($method->getAnnotations()); // => array(0 => {Route}, 1 => {Filter})
var_dump($method->getAnnotations(Filter::class)); // => array(0 => {Filter})
<?php
// C# style annotation rules can be implemented entirely in userland
class Usage
{
public $where;
public function __construct($where) {
$this->where = $where;
}
public static $classes = new Usage("classes");
public static $methods = new Usage("methods");
public static $properties = new Usage("properties");
public static $multiple = new Usage("multiple");
public static $inherit = new Usage("inherit");
}
class Annotations
{
public function getFromClass($class) {
// get class reflection
// collect annotations from class and recursively from parents
// for each annotation
// get Usage annotations from the annotation-class
// validate usage
// validate cardinality (single or multiple)
// apply inheritance logic (filter overridden annotations)
// return annotations
}
public function getFromMethod($class, $method) {
// ...
}
public function getFromProperty($class, $property) {
// ...
}
}
@mindplay-dk
Copy link
Author

mindplay-dk commented May 10, 2016

The syntax is simply << {expression} >>, e.g. any valid php expression that can be evaluated from that context, and the resulting value is simply appended to a list of annotations for the following source code member.

This also lets you annotate with simple values, if desired, e.g. << 123 >> or << "Hello World" >> or << [1, 2, 3] >> etc.

Or even complex expressions, e.g. << array_map(function ($v) { return $v+1; }, [1, 2, 3]) >>

Or annotating with reusable object instances, e.g. << HttpMethod::$GET >> or flyweight objects via methods, etc.

Or (if you must) attach functionality as delegates, e.g. << new Validation(function ($a, $b) { return $a < $b; }) >>. (this is more verbose than the examples in the attributes RFC using dynamic expressions, but it's explicit - the main purpose of annotations is to provide meta-data, not to provide functionality; and the syntax will look nicer/shorter if/when php gets a shorter syntax for anonymous functions.)

If somebody wants to decouple implementations from data and interpret the data at run-time, they can do that, e.g. using
<< ["name" => ["value1", "value2"]] >> which accomplishes the same thing proposed by the attributes RFC - but this has the advantage of relying more directly on php syntax, e.g. with any useful features that might get added to php syntax in the future being available as constructs for creation of annotations as well.

In other words, this leverages the entire language, rather than building a small feature on top of the language.

Regarding inheritance and rules about overriding annotations, that does not have to be a thing - getAnnotations() can return the annotations of the member you accessed, and you can go to it's parent class and collect more annotations if needed. If you accept only one instance of a specific type of annotation, you can stop walking upwards when you find the instance you're looking for - the rules can be your rules, in your code, logic and rules do not need to be baked into the language. Other soft rules, like the number of annotations allowed of the same type, or the type of members permitted by an annotation, do not need to be enforced or baked-in either. (it's difficult to conceive in advance of all the constraints, rules and conditions somebody might need, and probably better to leave this in userland.)

The getAnnotations() method takes an optional argument with a class/interface-name, which is compared with instanceof against the attributes in the set, maybe with special handling for simple types, e.g. int, float, bool, etc.

@pmukhin
Copy link

pmukhin commented May 16, 2016

Why this ugly syntax again? I mean "<< >>"? Is it really important to keep compatibilty with HHVM?
There's a lot of languages with native annotations built in such as java, python... Does it look any worse?

    @ORM\Column(name="id", type="integer")
    @ORM\GeneratedValue(strategy="AUTO")
    @ORM\Id()
    private $id;

Than this one:

    << new ORM\Column("id", "integer") >>
    << new ORM\GeneratedValue("AUTO") >>
    << new ORM\Id() >>
    private $id;

Aren't you (hhvm team in the reality) inventing a wheel inventing new syntax for annotations?

@fesor
Copy link

fesor commented May 26, 2016

For anyone who hate this syntax, solve this problem and then you can continue argue:

$fn = <<new Annotation()>> function () { /** ... */ };

@brzuchal
Copy link

@fesor there is simple solution for this problem - abandon @ silent operator for error supression.
It's a huge BC break but IMHO using error supression provides more complexity and using exceptions instead would provide clean way for handling errors there is try/catch for that - that would be awesome change in my opinion.

@brzuchal
Copy link

The STFU operator @ should be considered as bad practice, IMHO those usages should be replaced with valid exception handling. Removing STFU could be marked as deprecated and then some users would reflect and refactor for right way of exception handling but also part of users would just blow deprecation errors by adding additional STFU which might cause unproper usage of $ret = @@foo();.
Maybe I'm just thinking loud and that's all.

@eman1986
Copy link

eman1986 commented Oct 4, 2018

I agree <<>> is ugly, I much prefer @ or @@

using @ to suppress errors is super bad in my opinion, I last used that in 2002 when I was young and stupid, I now handle that error instead of sweep it under the rug.

I'm honestly ok with busting BC as if someone is going to upgrade to a new version of PHP, they should expect legacy code to ge a little bruised up in the process and its more of knowing whats going to break before upgrading your system. especially if this makes the cut for PHP 8, I'd like to see a lot of things get disrupted to clean up the API finally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment