A better description of Annotations? The book barely scratches the surface, and leaves questions unanswered
#1
Posted 15 September 2009 - 04:37 PM
I have been making due so far with a bit of code I cooked up, but it isn't very DRY, and it has a tendency to cause loops at times, and just isn't what I want... But that's just the tip of the iceberg. Annotations have a lot of power, and a little more explanation on how they work, when and why they are executed, and maybe a working example or two, would be great.
I have been checking for what seems like months now (really only just over a month) for an update to the description... Figured I would go out on a limb and politely ask...
Thanks for the help.
#2
Posted 16 September 2009 - 01:02 AM
These are great questions deserving great answers. I don't have it in me to try and recompose my original response tonight, so I'll just pull in the shoddy outline I threw together on the wiki.
http://wiki.recessfr...tom_Annotations
Steps to Write an Annotation:
1. Import the abstract Annotation class: Library::import('recess.lang.Annotation');
2. Implement its 4 abstract methods
1. usage - Returns a string representation of the intended usage of an annotation.
2. isFor - Returns an integer representation of the type(s) of PHP language constructs the annotation is applicable to. Use the Annotation::FOR_* consts to return the desired result.
3. validate($class) - Validate is called just before expansion. Because there may be multiple constraints of an annotation the implementation of validate should append any error messages to the protected $errors property. Commonly used validations helper methods are provided as protected methods on the Annotation class.
4. expand($class, $reflection, $descriptor) - The expansion step of an annotation gives it an opportunity to manipulate a class' descriptor by introducing additional metadata, attach methods, and wrap methods. Parameters: $class the classname the annotation is applied to. $reflection The PHP Reflection(Class|Method|Property) object the annotation is located on. $descriptor is the ClassDescriptor that the annotation is being expanded on.
Things you can do in the expand step:
1. Attach methods to a class dynamically - this is how relationships work
2. Define meta-data that is used by the class - i.e. the table name
3. Wrap new functionality around a wrappable method - this is how !Before and !After work
Things you can *not* do in the expand step:
1. Impact another class' ClassDescriptor
2. Use another class' ClassDescriptor
Ugh. The other post was so much better. Go ahead and continue with more pointed questions where you have them and lets use this thread as a discussion point to result in a great wiki page on the topic.
#3
Posted 16 September 2009 - 01:05 AM
#4
Posted 16 September 2009 - 01:13 AM
Object's buildClassDescriptor method (and, in turn, Annotation's expandAnnotation method, think strategy pattern) is less flexible and optimized for performance/caching. This is a little more nuanced and subject to those restrictions mentioned in the previous post: you cannot use/impact another class' descriptor in the process of building a class descriptor. I'll talk about why this is actually "a good thing" even though it may lead to scenarios where you could more succinctly declare things on one class in a follow-up.
If you're running into circularity problems there are two ways out: rethink the problem if possible, worst case live with code you feel isn't as DRY as possible -or- do the reflection/interpretation in a system you define and reflect over the annotations in your application/plugin code.
#5
Posted 16 September 2009 - 01:15 AM
christiaan, on 16 September 2009 - 02:05 AM, said:
I just killed the tab I'm writing this in and pressed it and back it was.
Did not know the shortcut but I actually did use FF's "Recently Closed Tabs". The tab popped up. My post was still there but greyed out. In my time-for-sleep stupor I clicked into the message box and the javascript for autoclearing the fast-reply box cleared out all the text. Fail. Thanks for the shortcut, though.
#6
Posted 16 September 2009 - 07:29 PM
I am not sure how this would work, I guess this is what I am asking... Do you wrap the function it's self? How? Etc...
I am new to OOP, but am getting the hang of it pretty well. I made a pretty slick app for a local pageant last fall that tallied votes and had a lot of statistics tracking and reporting. Recess ACTUALLY made it a lot of fun to do, and it was cool as an MVC primer.
Now I am writing an infinitely more complex app using Recess and YUI, and while it is coming along nicely in the preliminary stages, before I can deploy the app, I need to get an admin system up and running, with roles and permissions, and in order to do that I need an, at least rudimentary, grasp on annotations and plugins and how I can affect the Controller using them...
So far... not so good...
Working examples are always a help in learning new things... Anyone know of any?
PS: Sorry, didn't mean to discount what was already said, it has helped, but examples are really a kicker.
[EDIT:] Ok, since this will be the starting point for the Wiki, we already have the beginning of a Cookie Protected annotation in the recess book. Could you expand on that annotation with a full rundown on what code it would take to make it happen? Halfway there at this point, and I imagine it would be of infinite help to others as well. [/EDIT]
This post has been edited by beline: 16 September 2009 - 07:38 PM
#7
Posted 16 September 2009 - 11:54 PM
So I've whipped up a pair of simple annotations and posted them to Github. One is a simple example of how to attach a method to a class descriptor. The other has all the ingredients to do the cookie annotation that was started in the book, would love to see someone take these annotations and finish out the example from the book.
The project repo is here: http://github.com/Kr...tation-examples
All of the interesting code is here: http://github.com/Kr...ation-examples/
Attaching a Method with an Annotation
Our goal is to add an annotation to any public property and attach a method that slugs that property. Here's the usage:
<?php
class Post extends Object {
/** !SlugIt titleSlug */
public $title;
/** !SlugIt anAttachedMethod */
public $aProperty;
}
$foo = new Post();
$foo->title = 'A title? Yes, a title.';
var_export($foo->titleSlug());
// 'A-title--Yes--a-title-'
// (Yes, I've written the worst slugging regexp of all time, and simplest.)
$foo->aProperty = 'foo.bar';
echo $foo->anAttachedMethod(); // 'foo-bar'Cool. So the code that makes this possible is in two classes: the class SlugItAnnotation is the annotation, the other class (Sluggable) is a poor man's closure that implements the attached method.
SlugItAnnotation
Sluggable
If there are specific questions just call out the lines and I'll try to explain in more detail.
Wrapping a Method with an Annotation
Now, lets have a little more fun and implement really cheap authorization on a controller. We'll implement an annotation called !UnsafeProtection that takes three keyed parameters: username, password, and the name of the method to send unauthorized requests. It is placed on a controller class:
<?php
/** !UnsafeProtection User: foo, Pass: bar, UnauthorizedAction: noEntry */
class MyProtectedController extends Controller {
/** !Route GET */
function index() { die('Youve made it to the secret area!'); }
/** !Route GET, denied */
function noEntry() {
die('Username is foo, password bar');
}
}So a brand new request to / will prompt you for an HTTP username/password. If you cannot provide it you will be forwarded to the noEntry action.
The code behind this is:
UnsafeProtectionAnnotation
UnsafeProtectionWrapper
The actual demo controller that uses this can be found here:
http://github.com/Kr...r.class.php#L10
Feel free to fire away questions! Hope this helps. Would love to see someone take a stab at finishing out the book's example as an exercise.
#8
Posted 17 September 2009 - 10:04 AM
I don't have time to work through the code right now, but when I get a moment this afternoon I will look into it. From the first look though, it looks like it may answer most all of my questions, with a little re-working for my needs.
I will post more questions when I get a chance to look through it, but thanks in advance!
#12
Posted 17 September 2009 - 04:41 PM
To actually implement you will have to make that change, and then change the behavior of expand and the wrapper. The wrapper will need an additional bit of information: what method was this annotation written on? Similar to the way we got the property name from 'SlugIt' we can get the method(action) name in the protection annotation: $reflection->name;
We'll have to pass the name to our wrapper. The wrapper will change too because we'll want to see if the controller method that serve will call matches the protected method.
if($unauthorized && $request->meta->controllerMethod == $this->protectedMethod) {
// redirect your response
} else {
return;
}I can try and whip up another working example like this won't be able to get to it this evening, though.
#14
Posted 18 September 2009 - 03:11 PM
#15
Posted 22 September 2009 - 11:37 AM
Rafał Filipek, on 18 September 2009 - 04:11 PM, said:
Sure, in order to do this you would need to make a couple of changes:
1) In the Slugger, to construct with an optional character:
function __construct($propertyName, $separator) {
$this->propertyName = $propertyName;
$this->separator = $separator;
}
function slug($object) {
$property = $this->propertyName;
return preg_replace('/[^a-zA-Z]/', $this->separator, $object->$property); // Terrible slugging!
}2) In the SlugItAnnotation's validate method (I'm psuedo-coding here, I'll try to add the real code later):
protected function validate($class) {
$this->minimumParameterCount(1);
$this->maximumParameterCount(2);
$this->acceptsKeys(array('separator'));
}
3) Finally, we pass the parameter to the slugger in the expand function and we're all done:
protected function expand($class, $reflection, $descriptor) {
// Our goal here is to attach a method of name [slugMethodName]
// the first (and only) parameter after the annotation.
$slugMethodName = $this->values[0];
$separator = isset($this->separator) ? $this->separator : '-'; // This line and next are what changed
$slugger = new Slugger($reflection->name, $this->separator);
$descriptor->attachMethod($class, $slugMethodName, $slugger, 'slug');
return $descriptor;
}That should do it to allow you to change the character as an optional parameter:
!SlugIt foo or !SlugIt foo, Separator: .
#17
Posted 22 September 2009 - 05:09 PM
Only annotations that impact a class descriptor get expanded "automatically".
Automatically is a lie, though, they're lazily expanded, meaning it's only once you make a call that references some data that is stored on a class descriptor (i.e. a call to an attached method) that an annotation is expanded, and that is only if the class descriptor is not already cached.
These are those limitations I alluded to at the start of this thread, which I will reproduce (and reword for clarity) here:
Things you can do in the expand step:
1. Attach methods to a class' ClassDescriptor
2. Define meta-data that is used by the class on a class' ClassDescriptor
3. Wrap new functionality around a wrappable method on a class' ClassDescriptor
The ClassDescriptor is the all important thing. You can't use expand to set the value of a static variable on a class, or something like that, what you can mutate/assign in expand is limited to what you can express on a ClassDescriptor. This is the special limitation that allows us to not have to reflect/parse/expand annotations (slow) on every single run in production mode, we can instead just cache their resulting CacheDescriptor and continue quickly on our way.
#18
Posted 22 September 2009 - 05:20 PM
Why I ask is that I want to create an annotation like so:
/** !RequiresPermission TableDelete */
/** Route GET /delete/ */
function delete() {
.....
}
So users will have roles and these roles will have permissions assigned to them. The annotation will check what roles the user has, and subsequently, if any of those roles give the user the TableDelete permission. However, roles are not set (they can be created and destroyed) and the permissions that the roles have are also not set. While the roles will obviously have to be stored in the database, I didn't want to have to add each required permission into the database as I create them durring the development cycle, so I was going to have the required permission add it's self during the expand step the first time it was called. During deployment this would obviously not happen any more, but I was wondering if I need to visit every controller one at a time to get them to register...

Sign In
Register
Help

MultiQuote