From 836755632306b33f761b70cf449adf8e779ea3d3 Mon Sep 17 00:00:00 2001 From: Christian Kerl Date: Sat, 9 Apr 2011 00:40:31 +0200 Subject: [PATCH] added annotation support --- README.markdown | 5 +- Resources/config/annotations.xml | 4 +- ServiceDefinition/Annotation/Method.php | 33 +++++ ServiceDefinition/Annotation/Param.php | 28 +++++ ServiceDefinition/Annotation/Result.php | 19 +++ ServiceDefinition/Annotation/TypedElement.php | 40 ++++++ .../Loader/AnnotationClassLoader.php | 115 +++++++++++------- .../Loader/AnnotationFileLoader.php | 27 ++-- ServiceDefinition/Loader/AnnotationParser.php | 52 ++++++++ ServiceDefinition/Loader/AnnotationReader.php | 40 ++++++ 10 files changed, 300 insertions(+), 63 deletions(-) create mode 100644 ServiceDefinition/Annotation/Method.php create mode 100644 ServiceDefinition/Annotation/Param.php create mode 100644 ServiceDefinition/Annotation/Result.php create mode 100644 ServiceDefinition/Annotation/TypedElement.php create mode 100644 ServiceDefinition/Loader/AnnotationParser.php create mode 100644 ServiceDefinition/Loader/AnnotationReader.php diff --git a/README.markdown b/README.markdown index ef95312..8cd5bbc 100644 --- a/README.markdown +++ b/README.markdown @@ -37,8 +37,9 @@ QuickStart // src/Acme/DemoBundle/Controller/DemoController.php /** - * @ws:Method('hello') - * @ws:Param('name', type = 'string') + * @ws:Method("Hello") + * @ws:Param("name", type = "string") + * @ws:Result(type = "string") */ public function helloAction($name) { diff --git a/Resources/config/annotations.xml b/Resources/config/annotations.xml index 1bb69de..9b10ecf 100644 --- a/Resources/config/annotations.xml +++ b/Resources/config/annotations.xml @@ -5,8 +5,8 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Doctrine\Common\Annotations\AnnotationReader - Doctrine\Common\Annotations\Parser + Bundle\WebServiceBundle\ServiceDefinition\Loader\AnnotationReader + Bundle\WebServiceBundle\ServiceDefinition\Loader\AnnotationParser diff --git a/ServiceDefinition/Annotation/Method.php b/ServiceDefinition/Annotation/Method.php new file mode 100644 index 0000000..f91abd0 --- /dev/null +++ b/ServiceDefinition/Annotation/Method.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Annotation; + +class Method +{ + private $name; + private $service; + + public function __construct($values) + { + $this->name = isset($values['value']) ? $values['value'] : null; + $this->service = isset($values['service']) ? $values['service'] : null; + } + + public function getName($default = null) + { + return $this->name !== null ? $this->name : $default; + } + + public function getService() + { + return $this->service; + } +} \ No newline at end of file diff --git a/ServiceDefinition/Annotation/Param.php b/ServiceDefinition/Annotation/Param.php new file mode 100644 index 0000000..6bc3d55 --- /dev/null +++ b/ServiceDefinition/Annotation/Param.php @@ -0,0 +1,28 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Annotation; + +class Param extends TypedElement +{ + private $name; + + public function __construct($values) + { + parent::__construct($values); + + $this->name = $values['value']; + } + + public function getName() + { + return $this->name; + } +} \ No newline at end of file diff --git a/ServiceDefinition/Annotation/Result.php b/ServiceDefinition/Annotation/Result.php new file mode 100644 index 0000000..429c50b --- /dev/null +++ b/ServiceDefinition/Annotation/Result.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Annotation; + +class Result extends TypedElement +{ + public function __construct($values) + { + parent::__construct($values); + } +} \ No newline at end of file diff --git a/ServiceDefinition/Annotation/TypedElement.php b/ServiceDefinition/Annotation/TypedElement.php new file mode 100644 index 0000000..eb75b66 --- /dev/null +++ b/ServiceDefinition/Annotation/TypedElement.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Annotation; + +abstract class TypedElement +{ + private $phpType; + private $xmlType; + + public function __construct($values) + { + foreach(array('type', 'phpType') as $key) + { + if(isset($values[$key])) + { + $this->phpType = $values[$key]; + } + } + + $this->xmlType = isset($values['xmlType']) ? $values['xmlType'] : null; + } + + public function getPhpType() + { + return $this->phpType; + } + + public function getXmlType() + { + return $this->xmlType; + } +} \ No newline at end of file diff --git a/ServiceDefinition/Loader/AnnotationClassLoader.php b/ServiceDefinition/Loader/AnnotationClassLoader.php index abac95e..ee82665 100644 --- a/ServiceDefinition/Loader/AnnotationClassLoader.php +++ b/ServiceDefinition/Loader/AnnotationClassLoader.php @@ -1,59 +1,41 @@ + * (c) Christian Kerl * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. */ + namespace Bundle\WebServiceBundle\ServiceDefinition\Loader; -use Doctrine\Common\Annotations\AnnotationReader; -use Symfony\Component\Config\Resource\FileResource; + + +use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition; +use Bundle\WebServiceBundle\ServiceDefinition\Method; +use Bundle\WebServiceBundle\ServiceDefinition\Argument; +use Bundle\WebServiceBundle\ServiceDefinition\Type; + +use Bundle\WebServiceBundle\ServiceDefinition\Annotation\Method as MethodAnnotation; + use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderResolver; /** - * AnnotationClassLoader loads routing information from a PHP class and its methods. + * AnnotationClassLoader loads ServiceDefinition from a PHP class and its methods. * - * You need to define an implementation for the getRouteDefaults() method. Most of the - * time, this method should define some PHP callable to be called for the route - * (a controller in MVC speak). + * Based on \Symfony\Component\Routing\Loader\AnnotationClassLoader * - * The @Route annotation can be set on the class (for global parameters), - * and on each method. - * - * The @Route annotation main value is the route pattern. The annotation also - * recognizes three parameters: requirements, options, and name. The name parameter - * is mandatory. Here is an example of how you should be able to use it: - * - * /** - * * @Route("/Blog") - * * / - * class Blog - * { - * /** - * * @Route("/", name="blog_index") - * * / - * public function index() - * { - * } - * - * /** - * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) - * * / - * public function show() - * { - * } - * } - * - * @author Fabien Potencier + * @author Christian Kerl */ class AnnotationClassLoader implements LoaderInterface { + private $wsMethodAnnotationClass = 'Bundle\\WebServiceBundle\\ServiceDefinition\\Annotation\\Method'; + private $wsParamAnnotationClass = 'Bundle\\WebServiceBundle\\ServiceDefinition\\Annotation\\Param'; + private $wsResultAnnotationClass = 'Bundle\\WebServiceBundle\\ServiceDefinition\\Annotation\\Result'; + protected $reader; /** @@ -67,33 +49,72 @@ class AnnotationClassLoader implements LoaderInterface } /** - * Loads from annotations from a class. + * Loads a ServiceDefinition from annotations from a class. * * @param string $class A class name * @param string $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return ServiceDefinition A ServiceDefinition instance * * @throws \InvalidArgumentException When route can't be parsed */ public function load($class, $type = null) { - if (!class_exists($class)) { + if (!class_exists($class)) + { throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); } $class = new \ReflectionClass($class); - $collection = new RouteCollection(); - $collection->addResource(new FileResource($class->getFileName())); - - foreach ($class->getMethods() as $method) { + $definition = new ServiceDefinition(); + + foreach ($class->getMethods() as $method) + { + $wsMethodAnnot = $this->reader->getMethodAnnotation($method, $this->wsMethodAnnotationClass); + if($wsMethodAnnot !== null) + { + $wsParamAnnots = $this->reader->getMethodAnnotations($method, $this->wsParamAnnotationClass); + $wsResultAnnot = $this->reader->getMethodAnnotation($method, $this->wsResultAnnotationClass); + + $serviceMethod = new Method(); + $serviceMethod->setName($wsMethodAnnot->getName($method->getName())); + $serviceMethod->setController($this->getController($method, $wsMethodAnnot)); + + foreach($wsParamAnnots as $wsParamAnnot) + { + $serviceArgument = new Argument(); + $serviceArgument->setName($wsParamAnnot->getName()); + $serviceArgument->setType(new Type($wsParamAnnot->getPhpType(), $wsParamAnnot->getXmlType())); + + $serviceMethod->getArguments()->add($serviceArgument); + } + + if($wsResultAnnot !== null) + { + $serviceMethod->setReturn(new Type($wsResultAnnot->getPhpType(), $wsResultAnnot->getXmlType())); + } + + $definition->getMethods()->add($serviceMethod); + } } - return $collection; + return $definition; } + private function getController(\ReflectionMethod $method, MethodAnnotation $annotation) + { + if($annotation->getService() !== null) + { + return $annotation->getService() . ':' . $method->name; + } + else + { + return $method->class . '::' . $method->name; + } + } + /** * Returns true if this class supports the given resource. * diff --git a/ServiceDefinition/Loader/AnnotationFileLoader.php b/ServiceDefinition/Loader/AnnotationFileLoader.php index d0cbf73..6c13d7d 100644 --- a/ServiceDefinition/Loader/AnnotationFileLoader.php +++ b/ServiceDefinition/Loader/AnnotationFileLoader.php @@ -1,14 +1,14 @@ + * (c) Christian Kerl * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. */ + namespace Bundle\WebServiceBundle\ServiceDefinition\Loader; use Bundle\WebServiceBundle\ServiceDefinition\ServiceDefinition; @@ -21,7 +21,9 @@ use Symfony\Component\Config\FileLocator; * AnnotationFileLoader loads ServiceDefinition from annotations set * on a PHP class and its methods. * - * @author Fabien Potencier + * Based on \Symfony\Component\Routing\Loader\AnnotationFileLoader + * + * @author Christian Kerl */ class AnnotationFileLoader extends FileLoader { @@ -50,20 +52,21 @@ class AnnotationFileLoader extends FileLoader * @param string $file A PHP file path * @param string $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return ServiceDefinition A ServiceDefinition instance * - * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + * @throws \InvalidArgumentException When the file does not exist */ public function load($file, $type = null) { $path = $this->locator->locate($file); - $definiton = new ServiceDefinition(); + $definition = new ServiceDefinition(); + if ($class = $this->findClass($path)) { - + $definition = $this->loader->load($class, $type); } - - return $definiton; + + return $definition; } /** diff --git a/ServiceDefinition/Loader/AnnotationParser.php b/ServiceDefinition/Loader/AnnotationParser.php new file mode 100644 index 0000000..7b6f523 --- /dev/null +++ b/ServiceDefinition/Loader/AnnotationParser.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Loader; + +use Doctrine\Common\Annotations\Lexer; +use Doctrine\Common\Annotations\Parser; + +/** + * AnnotationParser allows multiple annotations of the same class to be present. + * + * @author Christian Kerl + */ +class AnnotationParser extends Parser +{ + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @return array + */ + public function Annotations() + { + $this->isNestedAnnotation = false; + + $annotations = array(); + $annot = $this->Annotation(); + + if ($annot !== false) { + $annotations[get_class($annot)][] = $annot; + $this->getLexer()->skipUntil(Lexer::T_AT); + } + + while ($this->getLexer()->lookahead !== null && $this->getLexer()->isNextToken(Lexer::T_AT)) { + $this->isNestedAnnotation = false; + $annot = $this->Annotation(); + + if ($annot !== false) { + $annotations[get_class($annot)][] = $annot; + $this->getLexer()->skipUntil(Lexer::T_AT); + } + } + + return $annotations; + } +} \ No newline at end of file diff --git a/ServiceDefinition/Loader/AnnotationReader.php b/ServiceDefinition/Loader/AnnotationReader.php new file mode 100644 index 0000000..81e3f03 --- /dev/null +++ b/ServiceDefinition/Loader/AnnotationReader.php @@ -0,0 +1,40 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Bundle\WebServiceBundle\ServiceDefinition\Loader; + +use Doctrine\Common\Annotations\AnnotationReader as BaseAnnotationReader; + +/** + * AnnotationReader. + * + * @author Christian Kerl + */ +class AnnotationReader extends BaseAnnotationReader +{ + public function getMethodAnnotation(\ReflectionMethod $method, $type) + { + $annotation = parent::getMethodAnnotation($method, $type); + + if($annotation !== null && count($annotation) > 1) + { + throw new \LogicException(sprintf("There is more than one annotation of type '%s'!", $type)); + } + + return $annotation !== null ? $annotation[0] : null; + } + + public function getMethodAnnotations(\ReflectionMethod $method, $type = null) + { + $annotations = parent::getMethodAnnotations($method); + + return $type !== null && isset($annotations[$type]) ? $annotations[$type] : $annotations; + } +} \ No newline at end of file