Share
Embed
Transcript
68 slides
-
2 Joshua Thijssen Freelance consultant, developer and trainer @ NoxLogic
Founder of the Dutch Web Alliance Development in PHP, Python, C, Java. Lead developer of Saffire. Blog: http://adayinthelifeof.nl Email: [email protected] Twitter: @jaytaph -
7 ➡ @ - notation, cause other languages do too
:) ➡ Userland only :( ➡ Reflection :( ➡ Different readers :( -
14 ➡ Read / parse annotations from classes / methods
/ properties. ➡ Places them in data objects (POPOs) ➡ Caching etc. -
15 1 <?php 2 3 // vendor/NoxLogic/Annotations/Append.php 4 5 namespace
NoxLogic\Annotations; 6 use Doctrine\Common\Annotations\Annotation; 7 8 /** 9 * @Annotation 10 */ 11 class Append { 12 public $value; 13 public $repeat = 8; 14 public $priority = 99; 15 } 16 simple annotation based on public properties -
16 1 <?php 2 3 // vendor/NoxLogic/Annotations/Append.php 4 5 namespace
NoxLogic\Annotations; 6 use Doctrine\Common\Annotations\Annotation; 7 8 /** 9 * @Annotation 10 */ 11 class Append { 12 protected $value; 13 protected $repeats; 14 protected $priority; 15 16 function __construct($options) { 17 $this->value = $options['value']; 18 $this->repeats = isset($options['repeat']) ? $options['repeat'] : 1; 19 $this->priority = isset($options['priority']) ? $options['priority'] : 100; 20 } 21 22 } 23 simple annotation based on __construct values -
17 1 <?php 2 3 // vendor/NoxLogic/Annotations/Prepend.php 4 5 namespace
NoxLogic\Annotations; 6 use Doctrine\Common\Annotations\Annotation; 7 8 /** 9 * @Annotation 10 * @Target({"METHOD"}) 11 */ 12 class Prepend extends Annotation { 13 public $value; 14 protected $repeat = 1; 15 protected $priority = 100; 16 17 function setValue($value) { 18 $this->value = $value; 19 } 20 21 function setRepeat($repeat) { 22 $this->repeats = $repeat; 23 } 24 25 function setPriority($priority) { 26 $this->priority = $priority; 27 } 28 29 } 30 simple annotation based on getters/setters -
18 1 <?php 2 3 // vendor/NoxLogic/Foo.php 4 5 namespace
NoxLogic; 6 7 use \NoxLogic\Annotations as NoxAnnotations; 8 9 class Foo { 10 11 /** 12 * @author Joshua Thijssen 13 * 14 * @NoxAnnotations\Prepend("text before", repeat = 3) 15 * @NoxAnnotations\Prepend("more text before") 16 * @NoxAnnotations\Append("some text after") 17 */ 18 function output($string) { 19 return "[".$string."]"; 20 } 21 22 } annotation usage -
19 1 <?php 2 3 use Doctrine\Common\Annotations\AnnotationReader; 4 use Doctrine\Common\Annotations\AnnotationRegistry;
5 6 require_once "vendor/autoload.php"; 7 8 9 AnnotationRegistry::registerAutoloadNamespace("\\NoxLogic\\", "vendor"); 10 $reader = new AnnotationReader(); 11 12 13 $class = new ReflectionClass("\\NoxLogic\\Foo"); 14 foreach ($class->getMethods() as $method) { 15 16 $annotations = $reader->getMethodAnnotations($method); 17 foreach ($annotations as $annotation) { 18 print_r ($annotation); 19 } 20 } 21 main application Can’t be PSR0 loader. Need silence! -
20 1 $ php annotations.php 2 NoxLogic\Annotations\Prepend Object 3 (
4 [value] => text before 5 [repeat:protected] => 3 6 [priority:protected] => 100 7 ) 8 NoxLogic\Annotations\Prepend Object 9 ( 10 [value] => more text before 11 [repeat:protected] => 1 12 [priority:protected] => 100 13 ) 14 NoxLogic\Annotations\Append Object 15 ( 16 [value:protected] => some text after 17 [repeats:protected] => 1 18 [priority:protected] => 100 19 ) -
21 1 <?php 2 3 // vendor/NoxLogic/Foo.php 4 5 namespace
NoxLogic; 6 7 use \NoxLogic\Annotations as NoxAnnotations; 8 9 class Foo { 10 11 /** 12 * @author Joshua Thijssen 13 * 14 * @NoxAnnotations\Prepend("text before", repeat = 3) 15 * @NoxAnnotations\Prepend("more text before") 16 * @NoxAnnotations\Append("some text after") 17 */ 18 function output($string) { 19 return "[".$string."]"; 20 } 21 22 } annotation usage Add use statements! (can mess up your IDE) Ignored Annotations -
22 private static $globalIgnoredNames = array( 'access'=> true, 'author'=> true,
'copyright'=> true, 'deprecated'=> true, 'example'=> true, 'ignore'=> true, 'internal'=> true, 'link'=> true, 'see'=> true, 'since'=> true, 'tutorial'=> true, 'version'=> true, 'package'=> true, 'subpackage'=> true, 'name'=> true, 'global'=> true, 'param'=> true, 'return'=> true, 'staticvar'=> true, 'category'=> true, 'staticVar'=> true, 'static'=> true, 'var'=> true, 'throws'=> true, 'inheritdoc'=> true, 'inheritDoc'=> true, 'license'=> true, 'todo'=> true, 'TODO'=> true, 'deprec'=> true, 'property' => true, 'method' => true, 'abstract'=> true, 'exception'=> true, 'magic' => true, 'api' => true, 'final'=> true, 'filesource'=> true, 'throw' => true, 'uses' => true, 'usedby'=> true, 'private' => true, 'Annotation' => true, 'override' => true, 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, 'Required' => true, 'Attribute' => true, 'Attributes' => true, 'Target' => true, 'SuppressWarnings' => true, 'ingroup' => true, 'code' => true, 'endcode' => true, 'package_version' => true, 'fixme' => true ); Doctrine\Common\Annotations\AnnotationReader -
23 1 $ php annotations.php 2 NoxLogic\Annotations\Prepend Object 3 (
4 [value] => text before 5 [repeat:protected] => 3 6 [priority:protected] => 100 7 ) 8 NoxLogic\Annotations\Prepend Object 9 ( 10 [value] => more text before 11 [repeat:protected] => 1 12 [priority:protected] => 100 13 ) 14 NoxLogic\Annotations\Append Object 15 ( 16 [value:protected] => some text after 17 [repeats:protected] => 1 18 [priority:protected] => 100 19 ) -
➡ Does NOT contain logic to does “magic” execution. ➡
“Prepend” and “Append” are just names. 24 -
1. Create annotation class 2. Create event listener(s) 3. Service
configuration 4. ??? 5. Profit! 27 World Domination Plan (v5.3 beta) -
28 1 <?php 2 3 namespace NoxLogic\DemoBundle\Service\Annotation; 4 5 /**
6 * @Annotation 7 */ 8 class Reverse { 9 10 } simple annotation without data values -
29 1 <?php 2 3 namespace NoxLogic\DemoBundle\EventListener; 4 5 use
NoxLogic\DemoBundle\Service\Annotation\Reverse; 6 use Symfony\Component\HttpKernel\Event\FilterControllerEvent; 7 8 class AnnotationListener { 9 10 /** @var \Doctrine\Common\Annotations\Reader */ 11 protected $reader; 12 13 function __construct(\Doctrine\Common\Annotations\Reader $reader) { 14 // Inject a annotation reader 15 $this->reader = $reader; 16 } 17 ... annotation listener 1/2 -
30 .... 18 public function onKernelController(FilterControllerEvent $event) { 19 //
Skip if we aren't a controller 20 if (! is_array($controller = $event->getController())) { 21 return; 22 } 23 24 $object = new \ReflectionObject($controller[0]); 25 $method = $object->getMethod($controller[1]); 26 27 foreach ($this->reader->getMethodAnnotations($method) as $annotation) { 28 if ($annotation instanceof Reverse) { 29 $r = new \ReflectionMethod($object, $method); 30 foreach ($r->getParameters() as $param) { 31 $name = $param->getName(); 32 33 $request = $event->getRequest(); 34 $request->attributes->set($name, strrev($request->attributes->get($name))); 35 } 36 } 37 } 38 } 39 40 } annotation listener 2/2 -
31 1 parameters: 2 noxlogic_demo_bundle.annotation.listener.class: NoxLogic\DemoBundle\EventListener\AnnotationListener 3 4 services: 5
noxlogic_demo_bundle.annotation.listener: 6 class : %noxlogic_demo_bundle.annotation.listener.class% 7 arguments: [ @annotation_reader ] 8 tags: 9 - { name: kernel.event_listener, event: kernel.controller, method: onKernelController, priority: 10 } service configuration -
32 1 <?php 2 3 namespace NoxLogic\DemoBundle\Controller; 4 5 use
Symfony\Bundle\FrameworkBundle\Controller\Controller; 6 use NoxLogic\DemoBundle\Service\Annotation as NoxAnnotation; 7 8 class DefaultController extends Controller 9 { 10 11 /** 12 * @NoxAnnotation\Reverse() 13 */ 14 public function indexAction($name) 15 { 16 return $this->render('NoxLogicDemoBundle:Default:index.html.twig', array('name' => $name)); 17 } 18 } hello dlrow controller -
33 1 <?php 2 3 namespace NoxLogic\DemoBundle\Controller; 4 5 use
Symfony\Bundle\FrameworkBundle\Controller\Controller; 6 use NoxLogic\DemoBundle\Service\Annotation as NoxAnnotation; 7 8 class DefaultController extends Controller 9 { 10 11 /** 12 * @NoxAnnotation\Reverse() 13 * @NoxAnnotation\Reverse() 14 */ 15 public function indexAction($name) 16 { 17 return $this->render('NoxLogicDemoBundle:Default:index.html.twig', array('name' => $name)); 18 } 19 } hello world multiple annotations -
34 1 foreach ($this->reader->getMethodAnnotations($method) as $annotation) { 2 if ($annotation
instanceof Reverse) { 3 $r = new \ReflectionMethod($controller[0], $controller[1]); 4 foreach ($r->getParameters() as $param) { 5 $name = $param->getName(); 6 7 $request = $event->getRequest(); 8 $request->attributes->set($name, strrev($request->attributes->get($name))); 9 } 10 } 11 12 if ($annotation instanceof Uppercase) { 13 $r = new \ReflectionMethod($controller[0], $controller[1]); 14 foreach ($r->getParameters() as $param) { 15 $name = $param->getName(); 16 17 $request = $event->getRequest(); 18 $request->attributes->set($name, strtoupper($request->attributes->get($name))); 19 } 20 } 21 22 if ($annotation instanceof .... complex listener -
36 ➡ Move annotations to separate listeners ➡ Uppercase Listener
➡ Reverse Listener ➡ Move “business logic” away into separate services (upperCaseService, reverseService, transformService) -
38 1 <?php 2 3 namespace NoxLogic\DemoBundle\Controller; 4 5 use
Symfony\Bundle\FrameworkBundle\Controller\Controller; 6 use NoxLogic\DemoBundle\Service\Annotation as NoxAnnotations; 7 8 class DefaultController extends Controller 9 { 10 11 /** 12 * @NoxAnnotations\Header(name="X-Some-Header", value="Data"); 13 * @NoxAnnotations\Header(name="X-AnotherHeader", value="123456"); 14 */ 15 public function indexAction($name) 16 { 17 return $this->render('NoxLogicDemoBundle:Default:index.html.twig', array('name' => $name)); 18 } 19 } header annotation usage -
➡ onKernelResponse (kernel.response) ➡ FilterResponseEvent $event ➡ But.. no $event->getController
➡ How to “iterate” our annotations? 39 -
➡ use onKernelController() (kernel.controller) ➡ store annotations inside request ➡
read back inside onKernelResponse 40 -
41 1 public function onKernelController(FilterControllerEvent $event) { 2 // Skip
if we aren't a controller 3 if (! is_array($controller = $event->getController())) { 4 return; 5 } 6 7 $object = new \ReflectionObject($controller[0]); 8 $method = $object->getMethod($controller[1]); 9 10 foreach ($this->reader->getMethodAnnotations($method) as $annotation) { 11 if (! $annotation instanceof Header) continue; 12 13 $request = $event->getRequest(); 14 $request->attributes->set('_header', $annotation); 15 } 16 } storing the annotation inside the $request -
42 1 public function onKernelResponse(FilterResponseEvent $event) { 2 $request =
$event->getRequest(); 3 4 $annotation = $request->attributes->get('_header', null); 5 if (! $annotation) return; 6 7 $response = $event->getResponse(); 8 $response->headers->set($annotation->getName(), $annotation->getValue()); 9 } retrieving the annotation from the $request -
44 1 /** 2 * @Annotation 3 */ 4 class
Header extends ConfigurationAnnotation { 5 6 protected $name; 7 protected $value; 8 9 public function getAliasName() 10 { 11 return "header"; 12 } 13 14 public function allowArray() 15 { 16 return true; 17 } using sensioFrameworkExtraBundle configurationAnnotation Calls setName() setValue(), stores annotation info inside request attribute field named “_header” and allows multiple annotations. -
45 1 public function onKernelResponse(FilterResponseEvent $event) { 2 $request =
$event->getRequest(); 3 4 $annotations = $request->attributes->get('_header', null); 5 if (! $annotations) return; 6 7 $response = $event->getResponse(); 8 foreach ($annotations as $annotation) { 9 $response->headers->set($annotation->getName(), $annotation->getValue()); 10 } 11 } auto injects (multiple) annotations inside $request -
➡ Uses kernel.controller (prio: 0) ➡ Does work when kernel.controller
has been called. ➡ Does not work on kernel.request etc. 46 -
48 ➡ Modifies mostly setup of a service. ➡ Tagging,
injection of services, security permissions etc. ➡ Pretty much you are changing the Dependency Injection Container. -
1. Create annotation class 2. Add compiler pass that checks
and tags annotated services. 3. Add compiler pass that collects tagged services and does the magic 49 World Domination Plan (v5.3 beta) -
➡ Cannot inject TOP_SECRET into PUBLIC or SECRET. ➡ Can
inject PUBLIC or SECRET into TOP_SECRET. ➡ Not annotated, we can inject it in any securitylevel. 53 -
1 <?php 2 3 namespace NoxLogic\DefaultBundle\Service; 4 5 use \NoxLogic\DefaultBundle\Annotation\SecurityLevel;
6 7 /** 8 * @SecurityLevel("PUBLIC") 9 */ 10 class PublicService { 11 12 /** 13 * AnotherPublicService 14 */ 15 public $anotherPublicService; 16 17 /** 18 * PrivateService // This should not be able to work! 19 */ 20 public $privateService; 21 22 } 54 -
1 <?php 2 3 namespace NoxLogic\DefaultBundle\Service; 4 5 use \NoxLogic\DefaultBundle\Annotation\SecurityLevel;
6 7 /** 8 * @SecurityLevel("PUBLIC") 9 */ 10 class AnotherPublicService { 11 12 /** 13 * PublicService 14 */ 15 protected $publicService; 16 17 18 function setPublicService($publicService) { 19 $this->publicService = $publicService; 20 } 21 22 } 55 -
1 <?php 2 3 /* 4 * Everything in this
service is highly classified! 5 */ 6 7 namespace NoxLogic\DefaultBundle\Service; 8 9 use \NoxLogic\DefaultBundle\Annotation\SecurityLevel; 10 11 /** 12 * @SecurityLevel("TOP SECRET") 13 */ 14 class PrivateService { 15 16 /** 17 * @var anotherPrivateService 18 */ 19 public $anotherPrivateService; 20 } 56 -
1 <?php 2 3 namespace NoxLogic\DefaultBundle\Service; 4 5 use \NoxLogic\DefaultBundle\Annotation\SecurityLevel;
6 7 /** 8 * @SecurityLevel("SECRET") 9 */ 10 class AnotherPrivateService { 11 12 /** 13 * PublicService 14 */ 15 protected $publicService; 16 17 function __construct($publicService) { 18 $this->publicService = $publicService; 19 } 20 21 } 57 -
1 parameters: 2 noxlogic.default.service.public.class: NoxLogic\DefaultBundle\Service\PublicService 3 noxlogic.default.service.anotherpublic.class: NoxLogic\DefaultBundle\Service\AnotherPublicService 4 noxlogic.default.service.private.class:
NoxLogic\DefaultBundle\Service\PrivateClass 5 noxlogic.default.service.anotherprivate.class: NoxLogic\DefaultBundle\Service\AnotherPrivateService 6 7 services: 8 noxlogic.default.service.public: 9 class: %noxlogic.default.service.public.class% 10 properties: 11 anotherPublicService: @noxlogic.default.service.anotherpublic 12 privateService: @noxlogic.default.service.private 13 14 noxlogic.default.service.anotherpublic: 15 class: %noxlogic.default.service.anotherpublic.class% 16 calls: 17 - [ setPublicService, [ @noxlogic.default.service.public ] ] 18 19 noxlogic.default.service.private: 20 class: %noxlogic.default.service.private.class% 21 properties: 22 anotherPrivateService: @noxlogic.default.service.anotherprivate 23 24 noxlogic.default.service.anotherprivate: 25 class: %noxlogic.default.service.anotherprivate.class% 26 arguments: [ @noxlogic.default.service.public ] 58 Services using other services, constructor, setter and property injection -
➡ Create annotation ➡ Create securityLevelCollectorPass ➡ Create securityLevelCheckerPass ➡
Add compiler passes 59 -
60 1 /** 2 * @Annotation 3 * @Target("CLASS") 4
*/ 5 class SecurityLevel { 6 7 const LEVEL_PUBLIC = 1; 8 const LEVEL_SECRET = 2; 9 const LEVEL_TOP_SECRET = 4; 10 11 protected $mapping = array( 12 "PUBLIC" => self::LEVEL_PUBLIC, 13 "SECRET" => self::LEVEL_SECRET, 14 "TOP SECRET" => self::LEVEL_TOP_SECRET, 15 ); 16 17 protected $level = 0; 18 19 public function __construct($value) { 20 $value = array_shift($value); 21 if (array_key_exists($value, $this->mapping)) { 22 $this->level = $this->mapping[$value]; 23 } else { 24 throw new \Exception("Unknown security level specified. Use: PUBLIC, SECRET or TOP_SECRET"); 25 } 26 } 27 28 function getLevel() { 29 return $this->level; 30 } 31 32 } -
61 1 class SecurityLevelCollectorPass implements CompilerPassInterface 2 { 3 4
public function process(ContainerBuilder $container) 5 { 6 // Can't inject? 7 $reader = $container->get("annotation_reader"); 8 9 foreach ($container->getDefinitions() as $id => $definition) { 10 try { 11 $class = new \ReflectionClass($definition->getClass()); 12 } catch (\ReflectionException $e) { 13 continue; 14 } 15 16 foreach ($reader->getClassAnnotations($class) as $annotation) { 17 if (! $annotation instanceOf \NoxLogic\DefaultBundle\Annotation\SecurityLevel) { 18 continue; 19 } 20 21 $definition->securityLevel = $annotation; 22 } 23 } 24 } -
62 1 class SecurityLevelCheckerPass implements CompilerPassInterface 2 { 3 /**
@var ContainerBuilder */ 4 protected $container; 5 6 public function process(ContainerBuilder $container) 7 { 8 $this->container = $container; 9 10 foreach ($this->container->getDefinitions() as $id => $definition) { 11 if (! property_exists($definition, 'securityLevel')) continue; 12 13 $this->checkSecurityClearance($definition, $definition->getArguments()); 14 $this->checkSecurityClearance($definition, $definition->getMethodCalls()); 15 $this->checkSecurityClearance($definition, $definition->getProperties()); 16 } 17 } ... -
63 1 function checkSecurityClearanceForArgument(Definition $definition, Reference $reference, Definition $targetDefinition) {
2 // No target is ok 3 if (! $targetDefinition) return; 4 5 // No securitylevel on the target is ok 6 if (! isset($targetDefinition->securityLevel)) { 7 return; 8 } 9 10 if ($definition->securityLevel->getLevel() > $targetDefinition->securityLevel->getLevel()) { 11 throw new SecurityLevelException("You cannot inject " . $targetDefinition->getClass() . " into the more secured service " . $definition->getClass()); 12 } 13 } -
64 1 <?php 2 3 namespace NoxLogic\DefaultBundle; 4 5 use
NoxLogic\DefaultBundle\DependencyInjection\Compiler\SecurityLevelCheckerPass; 6 use NoxLogic\DefaultBundle\DependencyInjection\Compiler\SecurityLevelCollectorPass; 7 use Symfony\Component\DependencyInjection\Compiler\PassConfig; 8 use Symfony\Component\DependencyInjection\ContainerBuilder; 9 use Symfony\Component\HttpKernel\Bundle\Bundle; 10 11 class NoxLogicDefaultBundle extends Bundle 12 { 13 public function build(ContainerBuilder $container) 14 { 15 $config = $container->getCompiler()->getPassConfig(); 16 $config->addPass(new SecurityLevelCollectorPass(), PassConfig::TYPE_AFTER_REMOVING); 17 $config->addPass(new SecurityLevelCheckerPass(), PassConfig::TYPE_AFTER_REMOVING); 18 } 19 } -
68 Find me on twitter: @jaytaph Find me for consultancy
and training: www.noxlogic.nl Find me on email: [email protected] Find me for blogs: www.adayinthelifeof.nl