partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit d32a4fdf355d9370dfeb1660c6d2e0f28e0aa374
parent e2e4c95f436892eb12466339445e2596a1aee462
Author: Felicitus <felicitus@felicitus.org>
Date:   Mon, 23 Jul 2012 13:43:41 +0200

Initial CLI service functionality. No API calls can be made yet; this is only for API call documentation

Diffstat:
Mpartkeepr.php | 8++++++--
Msrc/backend/PartKeepr/Auth/AuthService.php | 55+++++++++++++++++++++++++++++++++++++++++++++++++------
Asrc/backend/PartKeepr/Console/Commands/DescribeCallCommand.php | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Console/Commands/DescribeTypeCommand.php | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Console/Commands/Exceptions/ServiceCallNotFoundException.php | 8++++++++
Asrc/backend/PartKeepr/Console/Commands/Exceptions/ServiceNotFoundException.php | 8++++++++
Asrc/backend/PartKeepr/Console/Commands/ListCallsCommand.php | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Console/Commands/ListServicesCommand.php | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ApiType.php | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ApiTypeOutputField.php | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ApiTypeOutputFields.php | 29+++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/Service.php | 29+++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ServiceCall.php | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ServiceParameter.php | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/Annotations/ServiceReturnValue.php | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/ServiceCallReflector.php | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/ServiceReflector.php | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Service/TypeReflector.php | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/PartKeepr/Util/TablePrinter.php | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
19 files changed, 1234 insertions(+), 8 deletions(-)

diff --git a/partkeepr.php b/partkeepr.php @@ -28,8 +28,12 @@ try { } catch (\Exception $e) { } -$cli->addCommands(array( - new \PartKeepr\Console\Commands\MinifyJSCommand() +$cli->addCommands(array( + new \PartKeepr\Console\Commands\MinifyJSCommand(), + new \PartKeepr\Console\Commands\ListServicesCommand(), + new \PartKeepr\Console\Commands\ListCallsCommand(), + new \PartKeepr\Console\Commands\DescribeCallCommand(), + new \PartKeepr\Console\Commands\DescribeTypeCommand() )); $cli->run(); \ No newline at end of file diff --git a/src/backend/PartKeepr/Auth/AuthService.php b/src/backend/PartKeepr/Auth/AuthService.php @@ -5,14 +5,59 @@ use PartKeepr\Service\AnonService, PartKeepr\User\User, PartKeepr\User\UserManager, PartKeepr\User\Exceptions\InvalidLoginDataException, - PartKeepr\Session\SessionManager; + PartKeepr\Session\SessionManager, + PartKeepr\Service\Annotations\ServiceParameter as ServiceParameter, + PartKeepr\Service\Annotations\ServiceCall as ServiceCall, + PartKeepr\Service\Annotations\ServiceReturnValue as ServiceReturnValue, + PartKeepr\Service\Annotations\Service as ServiceDescription; + +/** + * @ServiceDescription(description="Manages authentication against PartKeepr") + */ class AuthService extends AnonService { /** - * Logs in the given user. If the login was successful, - * a session is automatically started. + * Logs in the given user. If the login was successful, a session is automatically started. * * @throws InvalidLoginDataException + * + * @ServiceCall(description="Authenticates a user against the system", + * documentation="Authenticates a user and starts a new session upon success.", + * returnValues={ + * @ServiceReturnValue( + * name="sessionid", + * type="string:50", + * description="The logged in username" + * ), + * @ServiceReturnValue( + * name="username", + * type="string:50", + * description="The session ID" + * ), + * @ServiceReturnValue( + * name="admin", + * type="boolean", + * description="True if the logged in user has admin rights" + * ), + * @ServiceReturnValue( + * name="userPreferences", + * type="UserPreference[]", + * description="An array of UserPreferences" + * ) + * }, + * parameters={ + * @ServiceParameter( name="username", + * type="string:50", + * required=true, + * description="The username to authenticate" + * ), + * @ServiceParameter( name="password", + * type="string:32", + * required=true, + * description="The password, hashed in MD5" + * ) + * }) + * */ public function login () { $this->requireParameter("username"); @@ -46,7 +91,5 @@ class AuthService extends AnonService { } else { throw new InvalidLoginDataException(); } - - - } + } } diff --git a/src/backend/PartKeepr/Console/Commands/DescribeCallCommand.php b/src/backend/PartKeepr/Console/Commands/DescribeCallCommand.php @@ -0,0 +1,127 @@ +<?php +namespace PartKeepr\Console\Commands; + +use Symfony\Component\Console\Input\InputArgument, + Symfony\Component\Console\Input\InputOption, + Symfony\Component\Console, + PartKeepr\Service\ServiceManager, + PartKeepr\Service\ServiceReflector, + PartKeepr\Service\ServiceCallReflector, + PartKeepr\Util\TablePrinter, + PartKeepr\Console\Commands\Exceptions\ServiceCallNotFoundException; + + +/** + * Implements the api-describe-call command. + */ +class DescribeCallCommand extends Console\Command\Command +{ + + /** + * Configures the api-describe-call command. + * @see Console\Command\Command + */ + protected function configure() + { + $this + ->setName('partkeepr:api-describe-call') + ->addArgument("service", InputArgument::REQUIRED, "The service") + ->addArgument("call", InputArgument::REQUIRED, "The call to describe") + ->setDescription('Describes a specific API call') + ->setHelp(<<<EOT +Describes a specific API call. Lists the parameters and return values +EOT + ); + } + + /** + * Describes a specific call. + * + * @param Console\Input\InputInterface $input The input interface + * @param Console\Output\OutputInterface $output The output interface + * @throws PartKeepr\Console\Commands\Exceptions\ServiceCallNotFoundException If the call was not found in the service + * @see Console\Command\Command + */ + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) + { + $service = $input->getArgument("service"); + + if (substr($service, -7) == "Service") { + $service = substr($service, 0, strlen($service) - 7); + } + + $className = "PartKeepr\\".$service."\\".$service."Service"; + + if (!class_exists($className)) { + throw new \Exception(sprintf("The service %s doesn't exist", $service)); + } + + $reflector = new ServiceReflector($className); + + $call = $reflector->getCall($input->getArgument("call")); + + if (!($call instanceof \PartKeepr\Service\ServiceCallReflector)) { + throw new ServiceCallNotFoundException(sprintf("The call %s was not found in the service %s", $input->getArgument("call"), $service)); + } + + $output->writeln(sprintf("<info>%s:%s</info>: %s", $service, $input->getArgument("call"), $call->getDescription())); + $output->writeln(""); + $output->writeln($call->getDocumentation()); + $output->writeln(""); + $this->outputParameterTable($call, $output); + $output->writeln(""); + $this->outputReturnedValuesTable($call, $output); + + } + + /** + * Outputs the returned parameter table + * + * @param \PartKeepr\Service\ServiceCallReflector $call The reflected call + * @param \Symfony\Component\Console\Output\OutputInterface $output The output interface + */ + private function outputReturnedValuesTable (ServiceCallReflector $call, Console\Output\OutputInterface $output) { + $output->writeln("<info>Returned values:</info>"); + $head = array('Name', 'Type', 'Description'); + + $returnValueTable = array(); + + foreach ($call->getReturnValues() as $returnValue) { + $returnValueTable[] = array($returnValue->getName(), + $returnValue->getType(), + $returnValue->getDescription()); + } + + $table = new TablePrinter($output); + $table->setHeader($head); + $table->setBody($returnValueTable); + + $table->output(); + } + + /** + * Outputs the parameter table + * + * @param \PartKeepr\Service\ServiceCallReflector $call The reflected call + * @param \Symfony\Component\Console\Output\OutputInterface $output The output interface + */ + private function outputParameterTable (ServiceCallReflector $call, Console\Output\OutputInterface $output) { + $output->writeln("<info>Parameters:</info>"); + $head = array('Name', 'Required', 'Type', 'Description'); + + $parameterTable = array(); + + foreach ($call->getParameters() as $parameter) { + $parameterTable[] = array($parameter->getName(), + $parameter->getRequiredFlag() ? 'Yes' : 'No', + $parameter->getType(), + $parameter->getDescription()); + } + + $table = new TablePrinter($output); + $table->setHeader($head); + $table->setBody($parameterTable); + + $table->output(); + } +} diff --git a/src/backend/PartKeepr/Console/Commands/DescribeTypeCommand.php b/src/backend/PartKeepr/Console/Commands/DescribeTypeCommand.php @@ -0,0 +1,71 @@ +<?php +namespace PartKeepr\Console\Commands; + +use Symfony\Component\Console\Input\InputArgument, + Symfony\Component\Console\Input\InputOption, + Symfony\Component\Console, + PartKeepr\Service\ServiceManager, + PartKeepr\Service\TypeReflector, + PartKeepr\Util\TablePrinter; + +/** + * Implements the api-describe-type command. + */ +class DescribeTypeCommand extends Console\Command\Command +{ + + /** + * Configures the api-describe-type command. + * @see Console\Command\Command + */ + protected function configure() + { + $this + ->setName('partkeepr:api-describe-type') + ->addArgument("type", InputArgument::REQUIRED, "The type to describe") + ->setDescription('Describes a specific API type') + ->setHelp(<<<EOT +Describes a specific API type +EOT + ); + } + + /** + * Describes a specific API type. + * + * @param Console\Input\InputInterface $input The input interface + * @param Console\Output\OutputInterface $output The output interface + * @throws PartKeepr\Console\Commands\Exceptions\ServiceCallNotFoundException If the call was not found in the service + * @see Console\Command\Command + */ + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) + { + $type = $input->getArgument("type"); + + $className = "PartKeepr\\".$type."\\".$type; + + $reflector = new TypeReflector($className); + + $output->writeln(sprintf("<info>%s</info>: %s", $reflector->getName(), $reflector->getDescription())); + $output->writeln(""); + $output->writeln($reflector->getDocumentation()); + $output->writeln(""); + $output->writeln("<info>Output Fields:</info>"); + + $head = array('Name', 'Type', 'Description'); + $outputParameterTable = array(); + foreach ($reflector->getOutputFields() as $parameter) { + $outputParameterTable[] = array($parameter->getName(), + $parameter->getType(), + $parameter->getDescription()); + } + + $table = new TablePrinter($output); + $table->setHeader($head); + $table->setBody($outputParameterTable); + + $table->output(); + + + } +} diff --git a/src/backend/PartKeepr/Console/Commands/Exceptions/ServiceCallNotFoundException.php b/src/backend/PartKeepr/Console/Commands/Exceptions/ServiceCallNotFoundException.php @@ -0,0 +1,7 @@ +<?php +namespace PartKeepr\Console\Commands\Exceptions; + +/** + * Thrown if a service call was not found in a specific service + */ +class ServiceCallNotFoundException extends \Exception {}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Console/Commands/Exceptions/ServiceNotFoundException.php b/src/backend/PartKeepr/Console/Commands/Exceptions/ServiceNotFoundException.php @@ -0,0 +1,7 @@ +<?php +namespace PartKeepr\Console\Commands\Exceptions; + +/** + * Thrown if a service was not found + */ +class ServiceNotFoundException extends \Exception {}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Console/Commands/ListCallsCommand.php b/src/backend/PartKeepr/Console/Commands/ListCallsCommand.php @@ -0,0 +1,74 @@ +<?php +namespace PartKeepr\Console\Commands; + +use Symfony\Component\Console\Input\InputArgument, + Symfony\Component\Console\Input\InputOption, + Symfony\Component\Console, + PartKeepr\Service\ServiceManager, + PartKeepr\Service\ServiceReflector, + PartKeepr\Console\Commands\Exceptions\ServiceNotFoundException; + +/** + * Implements the api-list-calls command. + */ +class ListCallsCommand extends Console\Command\Command +{ + + /** + * Configures the api-list-calls command. + * @see Console\Command\Command + */ + protected function configure() + { + $this + ->setName('partkeepr:api-list-calls') + ->addArgument("service", InputArgument::REQUIRED, "The service to list the calls for") + ->setDescription('Lists all calls within the specified service') + ->setHelp(<<<EOT +Lists all calls within the specified service +EOT + ); + } + + /** + * Lists all API calls for a specific service + * + * @param Console\Input\InputInterface $input The input interface + * @param Console\Output\OutputInterface $output The output interface + * @throws PartKeepr\Console\Commands\Exceptions\ServiceNotFoundException If the call was not found in the service + * @see Console\Command\Command + */ + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) + { + $service = $input->getArgument("service"); + + if (substr($service, -7) == "Service") { + $service = substr($service, 0, strlen($service) - 7); + } + + $className = "PartKeepr\\".$service."\\".$service."Service"; + + if (!class_exists($className)) { + throw new ServiceNotFoundException(sprintf("The service %s doesn't exist", $service)); + } + + $reflector = new ServiceReflector($className); + + // Stores the maximum length, for nice console formatting + $maxLength = 0; + + foreach ($reflector->getCalls() as $call) { + if (strlen($call->getName()) > $maxLength) { + $maxLength = strlen($call->getName()); + } + } + + $output->writeln(sprintf("<comment>Calls for service <info>%s</info>:</comment>", $reflector->getName())); + $output->writeln(""); + + foreach ($reflector->getCalls() as $call) { + $name = str_pad($call->getName(), $maxLength); + $output->writeln(sprintf("<info>%s</info> %s", $name, $call->getDescription())); + } + } +} diff --git a/src/backend/PartKeepr/Console/Commands/ListServicesCommand.php b/src/backend/PartKeepr/Console/Commands/ListServicesCommand.php @@ -0,0 +1,65 @@ +<?php +namespace PartKeepr\Console\Commands; + +use Symfony\Component\Console\Input\InputArgument, + Symfony\Component\Console\Input\InputOption, + Symfony\Component\Console, + PartKeepr\Service\ServiceManager, + PartKeepr\Service\ServiceReflector; + +/** + * Implements the api-list-services command. + */ +class ListServicesCommand extends Console\Command\Command +{ + + /** + * Configures the api-list-services command. + * + * @see Console\Command\Command + */ + protected function configure() + { + $this + ->setName('partkeepr:api-list-services') + ->setDescription('Lists all available API services') + ->setHelp(<<<EOT +Lists all available API services defined by PartKeepr. +EOT + ); + } + + /** + * Lists all API services + * + * @param Console\Input\InputInterface $input The input interface + * @param Console\Output\OutputInterface $output The output interface + * @see Console\Command\Command + */ + protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) + { + $classNames = ServiceManager::getInstance()->getRegisteredServices(); + + $services = array(); + // Stores the maximum length, for nice console formatting + $maxLength = 0; + + foreach ($classNames as $className) { + $reflector = new ServiceReflector($className); + + if (strlen($reflector->getName()) > $maxLength) { + $maxLength = strlen($reflector->getName()); + } + + $services[] = $reflector; + } + + $output->writeln("<comment>PartKeepr Services:</comment>"); + $output->writeln(""); + + foreach ($services as $service) { + $name = str_pad($service->getName(), $maxLength); + $output->writeln(sprintf("<info>%s</info> %s", $name, $service->getDescription())); + } + } +} diff --git a/src/backend/PartKeepr/Service/Annotations/ApiType.php b/src/backend/PartKeepr/Service/Annotations/ApiType.php @@ -0,0 +1,42 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a type. This is used by the TypeReflector to get information + * about the type. + * + * @see PartKeepr\Service\TypeReflector + * + * @Annotation + * @Target({"METHOD"}) + */ +class ApiType +{ + /** + * The description of the type + * @var string + */ + public $description; + + /** + * The documentation of the type + * @var string + */ + public $documentation; + + /** + * Returns the description of the type + * @return string + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the documentation of the type + * @return string + */ + public function getDocumentation () { + return $this->documentation; + } +} diff --git a/src/backend/PartKeepr/Service/Annotations/ApiTypeOutputField.php b/src/backend/PartKeepr/Service/Annotations/ApiTypeOutputField.php @@ -0,0 +1,56 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a single API output field. This is used by the TypeReflector to get information + * about the type. + * + * @see PartKeepr\Service\TypeReflector + * + * @Annotation + * @Target({"ANNOTATION"}) + */ +class ApiTypeOutputField +{ + /** + * The name of the field + * @var string + */ + public $name; + + /** + * The type of the field + * @var string + */ + public $type; + + /** + * The description of the field + * @var string + */ + public $description; + + /** + * Returns the name of the output field + * @return string + */ + public function getName () { + return $this->name; + } + + /** + * Returns the description of the output field + * @return string + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the type of the output field + * @return string + */ + public function getType () { + return $this->type; + } +} diff --git a/src/backend/PartKeepr/Service/Annotations/ApiTypeOutputFields.php b/src/backend/PartKeepr/Service/Annotations/ApiTypeOutputFields.php @@ -0,0 +1,29 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a list of type ApiTypeOutputField. This is used by the TypeReflector to get information + * about the type. + * + * @see PartKeepr\Service\TypeReflector + * + * @Annotation + * @Target({"METHOD"}) + */ +class ApiTypeOutputFields +{ + /** + * The output fields + * @var array<PartKeepr\Service\Annotations\ApiTypeOutputField> + */ + public $outputFields; + + /** + * Returns the output fields + * @return \PartKeepr\Service\Annotations\ApiTypeOutputField[] The output fields + */ + public function getOutputFields () { + return $this->outputFields; + } +} + diff --git a/src/backend/PartKeepr/Service/Annotations/Service.php b/src/backend/PartKeepr/Service/Annotations/Service.php @@ -0,0 +1,28 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a service. This is used by the ServiceReflector to get information + * about the services. + * + * @see PartKeepr\Service\ServiceReflector + * + * @Annotation + * @Target({"CLASS"}) + */ +class Service +{ + /** + * The description of the service + * @var string + */ + public $description; + + /** + * Returns the description of the service + * @return string + */ + public function getDescription () { + return $this->description; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Service/Annotations/ServiceCall.php b/src/backend/PartKeepr/Service/Annotations/ServiceCall.php @@ -0,0 +1,67 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a service call. + * @Annotation + * @Target({"METHOD"}) + */ +class ServiceCall +{ + /** + * The description of the call + * @var string + */ + public $description; + + /** + * The documentation of the call + * @var string + */ + public $documentation; + + /** + * The return values + * @var array<PartKeepr\Service\Annotations\ServiceReturnValue> + */ + public $returnValues; + + /** + * The parameters + * @var array<PartKeepr\Service\Annotations\ServiceParameter> + */ + public $parameters; + + /** + * Returns the description of this call + * @return string + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the documentation for this call + * @return string + */ + public function getDocumentation () { + return $this->documentation; + } + + /** + * Returns the parameters for this service call + * @return \PartKeepr\Service\Annotations\ServiceParameter[] The service parameters + */ + public function getParameters () { + return $this->parameters; + } + + /** + * Returns the return values for this service call + * @return \PartKeepr\Service\Annotations\ServiceReturnValue[] The returned values + */ + public function getReturnValues () { + return $this->returnValues; + } +} + diff --git a/src/backend/PartKeepr/Service/Annotations/ServiceParameter.php b/src/backend/PartKeepr/Service/Annotations/ServiceParameter.php @@ -0,0 +1,67 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a parameter to a service call +* @Annotation +* @Target({"ANNOTATION"}) +*/ +class ServiceParameter +{ + /** + * The name of the parameter + * @var string + */ + public $name; + + /** + * Defines if the parameter is required + * @var boolean + */ + public $required; + + /** + * The type of the parameter + * @var string + */ + public $type; + + /** + * The description of the parameter + * @var string + */ + public $description; + + /** + * Returns the name of the service parameter + * @return string + */ + public function getName () { + return $this->name; + } + + /** + * Returns if the parameter is required + * @return bool + */ + public function getRequiredFlag () { + return $this->required; + } + + /** + * Returns the description of the parameter + * @return string + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the type of the parameter + * @return string + */ + public function getType () { + return $this->type; + } + +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Service/Annotations/ServiceReturnValue.php b/src/backend/PartKeepr/Service/Annotations/ServiceReturnValue.php @@ -0,0 +1,52 @@ +<?php +namespace PartKeepr\Service\Annotations; + +/** + * Describes a single service return value + * + * @Annotation + * @Target({"ANNOTATION"}) + */ +class ServiceReturnValue +{ + /** + * The name of the return value + * @var string + */ + public $name; + + /** + * The description of the return value + * @var string */ + public $description; + + /** + * The type of the return value + * @var string + */ + public $type; + + /** + * Returns the description of the return value + * @return string + */ + public function getDescription () { + return $this->description; + } + + /** + * Returns the name of the return value + * @return string + */ + public function getName () { + return $this->name; + } + + /** + * Returns the type of the return value + * @return string + */ + public function getType () { + return $this->type; + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Service/ServiceCallReflector.php b/src/backend/PartKeepr/Service/ServiceCallReflector.php @@ -0,0 +1,95 @@ +<?php +namespace PartKeepr\Service; + +use Doctrine\Common\Annotations\AnnotationRegistry, + PartKeepr\PartKeepr, + PartKeepr\Service\Service; + +/** + * Reflects on a specific service call + */ +class ServiceCallReflector { + + /** + * Holds the annotation reader + * @var \Doctrine\Common\Annotations\AnnotationReader + */ + private $reader; + + /** + * Holds the method to reflect on + * @var \ReflectionMethod + */ + private $reflectionMethod; + + /** + * Creates a new service call reflector + * + * @param \ReflectionMethod $reflectionMethod The method to reflect on + * @param \Doctrine\Common\Annotations\AnnotationReader $reader The + * @throws \Exception If the method is not reflectable + */ + public function __construct (\ReflectionMethod $reflectionMethod, \Doctrine\Common\Annotations\AnnotationReader $reader) { + if (!self::isServiceCall($reflectionMethod, $reader)) { + throw new \Exception("Given method is not a reflectable method"); + } + + $this->reader = $reader; + $this->reflectionMethod = $reflectionMethod; + } + + /** + * Returns the name of the method + * @return string The name + */ + public function getName () { + return $this->reflectionMethod->getName(); + } + + /** + * Returns the description of the given service call + * @return string The description + */ + public function getDescription () { + return $this->reader->getMethodAnnotation($this->reflectionMethod, "PartKeepr\Service\Annotations\ServiceCall")->getDescription(); + } + + /** + * Returns the documentation for the API call + * @return string The documentaton + */ + public function getDocumentation () { + return $this->reader->getMethodAnnotation($this->reflectionMethod, "PartKeepr\Service\Annotations\ServiceCall")->getDocumentation(); + } + + /** + * Returns the parameters of the API call + * @return \PartKeepr\Service\Annotations\ServiceParameter[] The service parameters + */ + public function getParameters () { + return $this->reader->getMethodAnnotation($this->reflectionMethod, "PartKeepr\Service\Annotations\ServiceCall")->getParameters(); + } + + /** + * Returns the returned values + * @return PartKeepr\Service\Annotations\ServiceReturnValue[] The returned values + */ + public function getReturnValues () { + return $this->reader->getMethodAnnotation($this->reflectionMethod, "PartKeepr\Service\Annotations\ServiceCall")->getReturnValues(); + } + + /** + * Returns if the given method is a ServiceCall + * @static + * @param \ReflectionMethod $reflectionMethod The method to reflect on + * @param \Doctrine\Common\Annotations\AnnotationReader $reader The annotation reader + * @return bool true if the method is a service call, false otherwise + */ + public static function isServiceCall (\ReflectionMethod $reflectionMethod, \Doctrine\Common\Annotations\AnnotationReader $reader ) { + if ($reader->getMethodAnnotation($reflectionMethod, "PartKeepr\Service\Annotations\ServiceCall") === null) { + return false; + } else { + return true; + } + } +} diff --git a/src/backend/PartKeepr/Service/ServiceReflector.php b/src/backend/PartKeepr/Service/ServiceReflector.php @@ -0,0 +1,113 @@ +<?php +namespace PartKeepr\Service; + +use Doctrine\Common\Annotations\AnnotationRegistry, + PartKeepr\PartKeepr, + PartKeepr\Service\Service; + +/** + * Reflects a service. + */ +class ServiceReflector { + /** + * The class name to reflect on + * @var string + */ + private $className; + + /** + * The reflected class + * @var \ReflectionClass + */ + private $reflClass; + + /** + * The annotation reader + * @var \Doctrine\Common\Annotations\AnnotationReader + */ + private $reader; + + /** + * Creates a new service reflector + * + * @param $className string The FQCN of the service class to reflect + * @throws \Exception If the passed class name isn't a subclass of PartKeepr\Service\Service + */ + public function __construct ($className) { + $this->reflClass = new \ReflectionClass($className); + + if (!$this->reflClass->isSubclassOf("PartKeepr\\Service\\Service")) { + throw new \Exception(sprintf("%s isn't a subclass of PartKeepr\\Service\\Service, can't reflect", $className)); + } + $this->className = $className; + $this->reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $this->registerAnnotationLoaders(); + } + + /** + * Returns the name of the service + * @return string The name of the service + */ + public function getName () { + return $this->reflClass->getShortName(); + } + + /** + * Returns the description of the service + * @return string The description of the service + */ + public function getDescription () { + return $this->reader->getClassAnnotation($this->reflClass, "PartKeepr\\Service\\Annotations\\Service")->getDescription(); + } + + /** + * Returns all calls for the service + * @return ServiceCallReflector[] An array of ServiceCallReflector objects + */ + public function getCalls () { + $calls = array(); + + foreach ($this->reflClass->getMethods() as $method) { + if (ServiceCallReflector::isServiceCall($method, $this->reader)) { + $calls[] = new ServiceCallReflector($method, $this->reader); + } + } + + return $calls; + } + + /** + * Returns a call by name + * @param $name The call name to retrieve + * @return null|ServiceCallReflector The call name + */ + public function getCall ($name) { + foreach ($this->getCalls() as $call) { + if ($call->getName() == $name) { + return $call; + } + } + + return null; + } + /** + * Register the annotation loader. + * @todo This will happen multiple times, needs a fix to do this only once + */ + public function registerAnnotationLoaders () { + AnnotationRegistry::registerLoader(function($class) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + + $fullFile = PartKeepr::getRootDirectory(). "/src/backend/" . $file; + + if (file_exists($fullFile)) { + // file exists makes sure that the loader fails silently + require $fullFile; + return true; + + } + }); + + AnnotationRegistry::registerAutoloadNamespace('PartKeepr\Service\Annotations', PartKeepr::getRootDirectory() . "/src/backend/PartKeepr/Service/Annotations"); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Service/TypeReflector.php b/src/backend/PartKeepr/Service/TypeReflector.php @@ -0,0 +1,90 @@ +<?php +namespace PartKeepr\Service; + +use Doctrine\Common\Annotations\AnnotationRegistry, + PartKeepr\PartKeepr, + PartKeepr\Service\Service; + +/** + * Reflects a type. + */ +class TypeReflector { + private $className; + private $reflClass; + private $reader; + + /** + * Creates a new service reflector + * + * @param $className string The FQCN of the service class to reflect + */ + public function __construct ($className) { + $this->reflClass = new \ReflectionClass($className); + + $this->className = $className; + $this->reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader(); + $this->reader->addNamespace('Doctrine\ORM\Mapping'); + $this->reader->addNamespace('PartKeepr\\Service\\Annotations'); + + $this->registerAnnotationLoaders(); + } + + /** + * Returns the name of the type + * + * @return string The name of the type + */ + public function getName () { + return $this->reflClass->getShortName(); + } + + /** + * Returns the description of the type + * @return string The description of the type + */ + public function getDescription () { + return $this->reader->getClassAnnotation($this->reflClass, "PartKeepr\\Service\\Annotations\\ApiType")->getDescription(); + } + + /** + * Returns the documentation of the type + * @return string The documentation of the type + */ + public function getDocumentation () { + return $this->reader->getClassAnnotation($this->reflClass, "PartKeepr\\Service\\Annotations\\ApiType")->getDocumentation(); + } + + /** + * Returns the output fields + * @return \PartKeepr\Service\Annotations\ApiTypeOutputField The output fields + */ + public function getOutputFields () { + $serializeReflMethod = $this->reflClass->getMethod("serialize"); + + $outputs = $this->reader->getMethodAnnotation($serializeReflMethod, "PartKeepr\\Service\\Annotations\\ApiTypeOutputFields"); + + return $outputs->getOutputFields(); + } + + /** + * Register the annotation loader. + * @todo This will happen multiple times, needs a fix to do this only once + */ + public function registerAnnotationLoaders () { + AnnotationRegistry::registerLoader(function($class) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + + $fullFile = PartKeepr::getRootDirectory(). "/src/backend/" . $file; + + if (file_exists($fullFile)) { + // file exists makes sure that the loader fails silently + require $fullFile; + return true; + + } + }); + + AnnotationRegistry::registerAutoloadNamespace('PartKeepr\\Service\\Annotations', PartKeepr::getRootDirectory() . "/src/backend/PartKeepr/Service/Annotations"); + require_once('Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php'); + } +}+ \ No newline at end of file diff --git a/src/backend/PartKeepr/Util/TablePrinter.php b/src/backend/PartKeepr/Util/TablePrinter.php @@ -0,0 +1,181 @@ +<?php +namespace PartKeepr\Util; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * TablePrinter. + * + * Original by Antoine Durieux, ported to a proper class by Felicitus + * + * Source: http://adurieux.blogspot.com/2011/10/table-printer-for-symfony2-console.html + */ +class TablePrinter +{ + /** + * Holds an instance of the OutputInterface + * @var null|\Symfony\Component\Console\Output\OutputInterface + */ + private $output = null; + + /** + * The header columns as simple array + * @var array + */ + private $header = array(); + + /** + * The body columns as nested array. + * @var array + */ + private $body = array(); + + /** + * Creates a new instance of the table printer + * + * @param \Symfony\Component\Console\Output\OutputInterface $output The output interface to use + */ + public function __construct (OutputInterface $output) { + $this->output = $output; + } + + /** + * Sets the header + * @param string[] $header The header columns to set + */ + public function setHeader (array $header) { + $this->header = $header; + } + + /** + * Sets the body + * @param array $body A nested array of rows, e.g. + * + * setBody( array( + * array("Row1Col1", "Row1Col2"), + * array("Row2Col1", "Row2Col2") + * )); + */ + public function setBody (array $body) { + $this->body = $body; + } + + /** + * This function performs the full print of the table + * + * @author Antoine Durieux + */ + public function output () + { + // --------------------------------------------------------------------- + // 1. Find column widths to use + // --------------------------------------------------------------------- + $columnWidths = $this->computeColumnWidths($this->body,$this->header); + + // --------------------------------------------------------------------- + // 2. Print header + // --------------------------------------------------------------------- + if(count($this->header) != 0) + { + $this->printConsole($this->printBlankLine($columnWidths)); + $this->printConsole($this->printLine($this->header,$columnWidths)); + } + + // --------------------------------------------------------------------- + // 3. Print body + // --------------------------------------------------------------------- + $this->printConsole($this->printBlankLine($columnWidths)); + foreach($this->body as $row) + { + $this->printConsole($this->printLine($row,$columnWidths)); + } + $this->printConsole($this->printBlankLine($columnWidths)); + } + + /** + * This function computes the sizes of the columns depending on the size of + * what they will contain. + * + * @author Antoine Durieux + * + */ + private function computeColumnWidths() + { + // In case we have no head + if(count($this->header) != 0) + { + $columnWidths = array_map('mb_strlen',$this->header); + } + else + { + $columnWidths = array_map('mb_strlen',$this->body[key($this->body)]); + } + foreach($this->body as $row) + { + foreach($row as $index => $value) + { + $columnWidths[$index] = max(mb_strlen($value),$columnWidths[$index]); + } + } + + return $columnWidths; + } + + /** + * private static function printBlankLine($columnWidths) + * + * This function returns a string that corresponds to a decorating line. + * + * @author Antoine Durieux + * + * @version 1.0 + * + * @param array $columnWidths The widths of the columns + * @return string The resulting string + */ + private function printBlankLine($columnWidths) + { + $line = '+'; + foreach(array_keys($columnWidths) as $index) + { + $line .= str_repeat('-',$columnWidths[$index]+2).'+'; + } + return $line; + } + + /** + * This function returns a string that corresponds to a regular line of the + * table. + * + * @author Antoine Durieux + * + * @version 1.0 + * + * @param array $line The line to be printed + * @param array $columnWidths The widths of the columns + * @return string The resulting string + */ + private function printLine($line,$columnWidths) + { + $string = '|'; + foreach($line as $index => $value) + { + $string .= ' '.$value.str_repeat(' ',$columnWidths[$index]-mb_strlen($value)).' |'; + } + return $string; + } + + /** + * This function prints a line in the console + * + * @author Antoine Durieux + * + * @version 1.0 + * + * @param string $string The string to print in the console + */ + public function printConsole($string) + { + $this->output->writeln($string); + } +}+ \ No newline at end of file