Recess Developer Forums: A better description of Annotations? - Recess Developer Forums

Jump to content

  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

A better description of Annotations? The book barely scratches the surface, and leaves questions unanswered

#1 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 15 September 2009 - 04:37 PM

I am really interested in learning more about annotations. Particularly after seeing some of KevBurns work, and needing to develop an admin interface of my own...

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.
0

#2 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 16 September 2009 - 01:02 AM

Ugh. My stomach just sank a lot. I had just finished writing a pretty lengthy (~1-1.5hr composition), mostly interesting (but prone to the occasional tangent) response that covered a good number of whys and hows and linked to lots of interesting places in source code from both 0.1 and 0.2 covering annotations. Was just pulling in a link from a wiki page I setup to serve as an outline that we can use as a starting point. In dragging my mouse over to reselect the forum tab I click the X. Gone. Ah well.

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.
0

#3 User is offline   christiaan Icon

  • Group: Members
  • Posts: 35
  • Joined: 06-September 09
  • LocationNijkerk, Netherlands

Posted 16 September 2009 - 01:05 AM

View PostKrisJordan, on 16 September 2009 - 08:02 AM, said:

In dragging my mouse over to reselect the forum tab I click the X. Gone. Ah well.


Tried pressing Ctrl + Shift + T? Or are you browsing using IE :P
I just killed the tab I'm writing this in and pressed it and back it was.
0

#4 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 16 September 2009 - 01:13 AM

I should also note there are 2 ways annotations are used. Via reflection and via Object's buildClassDescriptor (which, in turn uses reflection). You can reflect over the annotations of a subclass of object using any of the RecessReflection* classes in recess.lang. Each has a getAnnotations() method that returns an array of any successfully parsed annotations. Using Reflection and Annotations your options are wide open and you can use annotations from multiple classes to influence your program.

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.
0

#5 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 16 September 2009 - 01:15 AM

View Postchristiaan, on 16 September 2009 - 02:05 AM, said:

Tried pressing Ctrl + Shift + T? Or are you browsing using IE :P
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.
0

#6 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 16 September 2009 - 07:29 PM

The example in the book mentions protecting a function with a cookie.

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

0

#7 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 16 September 2009 - 11:54 PM

Good point, as KevBurnsJr says: Less talk, more code.

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.
0

#8 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 17 September 2009 - 10:04 AM

BRILLIANT!

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!
0

#9 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 17 September 2009 - 01:56 PM

Ok, so what is this:

Library::import('recess.http.responses.ForwardingUnauthorizedResponse');

And what does it do?

[EDIT:]Scratch that, got it.[/EDIT]
0

#10 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 17 September 2009 - 01:57 PM

Also, what if we just wanted to protect a single function (route)?
0

#11 User is offline   Rafał Filipek Icon

  • Group: Members
  • Posts: 35
  • Joined: 28-August 09
  • LocationPoland

Posted 17 September 2009 - 03:23 PM

Than You have to specify annotionon for method and set in isFor() method :
function isFor() { return Annotation::FOR_METHOD; }

Yes, in Poland we have a internet connection ;]
0

#12 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 17 September 2009 - 04:41 PM

In addition to what Rafal mentions, there is a little more legwork. The 'isFor' method is primarily to aid in the developer experience and will give you an error message if you attempt to put an annotation on a construct it wasn't meant for.

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.
0

#13 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 17 September 2009 - 09:46 PM

Thats fine. That helps me understand it a bit more, and there isn't really any urgency on this project, so I am not worried.
0

#14 User is offline   Rafał Filipek Icon

  • Group: Members
  • Posts: 35
  • Joined: 28-August 09
  • LocationPoland

Posted 18 September 2009 - 03:11 PM

I also have some question about Annotations :) How can i pass additional parameter in expand method? Eg. Kris made SlugIt Annotation. Default separator is '-' sign. Is it posible to define it to some other character in Annotaion description?
Yes, in Poland we have a internet connection ;]
0

#15 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 22 September 2009 - 11:37 AM

View PostRafał Filipek, on 18 September 2009 - 04:11 PM, said:

Default separator is '-' sign. Is it posible to define it to some other character in Annotaion description?


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: .

0

#16 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 22 September 2009 - 05:00 PM

Something else, are all annotations read and expanded, regardless of the application/controller being used? If not, which ones are read/executed/expanded?
0

#17 User is offline   KrisJordan Icon

  • Administrator
  • Icon
  • Group: Administrators
  • Posts: 78
  • Joined: 25-August 09
  • LocationNorth Carolina, USA

Posted 22 September 2009 - 05:09 PM

This is a great question that actually just came up in IRC. It's not entirely straightforward. I think the simplest way to answer is:

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.
0

#18 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 22 September 2009 - 05:20 PM

So annotations are only actually expanded in production mode, in deployment mode the class descriptors are serialized and nothing is ever reflected/parsed/expanded? Makes sense...

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...
0

#19 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 22 September 2009 - 05:21 PM

which brings up another question, are the models available during the expand step?
0

#20 User is offline   beline Icon

  • Group: Members
  • Posts: 36
  • Joined: 02-September 09
  • LocationMaine, USA

Posted 28 September 2009 - 11:43 AM

Hmmm, I never understood how I kill topics...
0

  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users