/** * Get metadata for a certain class - loads once and caches * @param string $className * @throws \Drest\DrestException * @return ClassMetaData $metaData */ public function getMetadataForClass($className) { if (isset($this->loadedMetadata[$className])) { return $this->loadedMetadata[$className]; } // check the cache if ($this->cache !== null) { $classMetadata = $this->cache->fetch($this->cache_prefix . $className); if ($classMetadata instanceof ClassMetaData) { if ($classMetadata->expired()) { $this->cache->delete($this->cache_prefix . $className); } else { $this->loadedMetadata[$className] = $classMetadata; return $classMetadata; } } } $classMetadata = $this->driver->loadMetadataForClass($className); if ($classMetadata !== null) { $this->loadedMetadata[$className] = $classMetadata; if ($this->cache !== null) { $this->cache->save($this->cache_prefix . $className, $classMetadata); } return $classMetadata; } if (is_null($this->loadedMetadata[$className])) { throw DrestException::unableToLoadMetaDataFromDriver(); } return $this->loadedMetadata[$className]; }
/** * Get all the metadata class names known to this driver. * @return array * @throws DrestException * @throws DriverException */ public function getAllClassNames() { if (empty($this->classes)) { if (empty($this->paths)) { throw DrestException::pathToConfigFilesRequired(); } foreach ($this->paths as $path) { if (!file_exists($path)) { throw DriverException::configurationFileDoesntExist($path); } $resources = json_decode(file_get_contents($path), true); if ($resources === null) { throw DriverException::configurationFileIsInvalid('Json'); } $entities = []; foreach ($resources['resources'] as $resource) { $entity = $resource['entity']; $entities[$entity] = $resource; unset($entities[$entity]['entity']); } $this->classes = array_merge($this->classes, $entities); } } return array_keys($this->classes); }
/** * Check the format of the named route * @param $namedRoute * @throws DrestException */ protected function checkNamedRoute($namedRoute) { if (substr_count($namedRoute, '::') !== 1) { throw DrestException::invalidNamedRouteSyntax(); } if (sizeof(explode('::', $namedRoute)) !== 2) { throw DrestException::invalidNamedRouteSyntax(); } }
/** * Get all the metadata class names known to this driver. * @return array * @throws DrestException * @throws DriverException */ public function getAllClassNames() { if (empty($this->classes)) { if (empty($this->paths)) { throw DrestException::pathToConfigFilesRequired(); } $yamlParser = new YamlParser(); foreach ($this->paths as $path) { if (!file_exists($path)) { throw DriverException::configurationFileDoesntExist($path); } $resources = $yamlParser->parse(file_get_contents($path)); if ($resources === false || empty($resources)) { throw DriverException::configurationFileIsInvalid('Yaml'); } $this->classes = array_merge($this->classes, (array) $resources); } } return array_keys($this->classes); }
/** * Set whether we would like to expose this route (and its verbs) to OPTIONS requests * @param integer|boolean $value - if using integer -1 to unset, 0 for no and 1 if yes * @throws DrestException */ public function setAllowedOptionRequest($value = true) { if (is_bool($value)) { $this->allowed_option_request = $value ? 1 : 0; } elseif ($value != -1) { throw DrestException::invalidAllowedOptionsValue(); } $this->allowed_option_request = $value; }
/** * Configure the expose object to filter out fields that are not allowed to be use by the client. * Unlike the configuring of the Pull request, this function will return the formatted array in a ResultSet object * This is only applicable for a HTTP push (POST/PUT/PATCH) call * @param array $pushed - the data push on the request * @throws \Drest\DrestException * @return \DrestCommon\ResultSet * * @todo: this should follow the same pattern as configurePullRequest */ public function configurePushRequest($pushed) { // Offset the array by one of it has a string key and is size of 1 if (sizeof($pushed) == 1 && is_string(key($pushed))) { $rootKey = key($pushed); $pushed = $this->filterPushExpose($pushed[key($pushed)], $this->fields); return ResultSet::create($pushed, $rootKey); } else { throw DrestException::unableToHandleACollectionPush(); } }
/** * Add verbs that are to be allowed on this route. * @param mixed $verbs = a single or array of verbs valid for this route. eg array('GET', 'PUT') * @throws DrestException if verb is invalid */ public function setVerbs($verbs) { foreach ((array) $verbs as $verb) { $verb = strtoupper($verb); if (!defined('DrestCommon\\Request\\Request::METHOD_' . $verb)) { throw DrestException::invalidHttpVerbUsed($verb); } $this->verbs[] = $verb; } }
/** * Process the method * @param $methods * @param Mapping\ClassMetaData $metadata * @throws DrestException */ protected function processMethods($methods, Mapping\ClassMetaData $metadata) { // Set the handle calls foreach ($methods as $method) { /* @var \ReflectionMethod $method */ if ($method->isPublic()) { foreach ($this->reader->getMethodAnnotations($method) as $methodAnnotation) { if ($methodAnnotation instanceof Annotation\Handle) { // Make sure the for is not empty if (empty($methodAnnotation->for) || !is_string($methodAnnotation->for)) { throw DrestException::handleForCannotBeEmpty(); } if (($routeMetaData = $metadata->getRouteMetaData($methodAnnotation->for)) === false) { throw DrestException::handleAnnotationDoesntMatchRouteName($methodAnnotation->for); } if ($routeMetaData->hasHandleCall()) { // There is already a handle set for this route throw DrestException::handleAlreadyDefinedForRoute($routeMetaData); } $routeMetaData->setHandleCall($method->getName()); } } } } }
/** * Gets an instance of the "default" action based of request information * @throws DrestException * @return AbstractAction $action */ protected function getDefaultAction() { $httpMethod = $this->dm->calledWithANamedRoute() ? array_slice($this->matched_route->getVerbs(), 0, 1)[0] : $this->getRequest()->getHttpMethod(); $className = '\\Drest\\Service\\Action\\' . ucfirst(strtolower($httpMethod)); switch ($httpMethod) { case Request::METHOD_GET: case Request::METHOD_DELETE: $className .= $this->matched_route->isCollection() ? 'Collection' : 'Element'; break; default: $className .= 'Element'; break; } if (!class_exists($className)) { throw DrestException::unknownActionClass($className); } return new $className($this); }
/** * Process all routes defined * @param array $routes * @param ClassMetaData $metadata * @throws DrestException */ protected function processRoutes(array $routes, ClassMetaData $metadata) { $originFound = false; foreach ($routes as $route) { $routeMetaData = new RouteMetaData(); // Set name $route['name'] = preg_replace("/[^a-zA-Z0-9_\\s]/", "", $route['name']); if ($route['name'] == '') { throw DrestException::routeNameIsEmpty(); } if ($metadata->getRouteMetaData($route['name']) !== false) { throw DrestException::routeAlreadyDefinedWithName($metadata->getClassName(), $route['name']); } $routeMetaData->setName($route['name']); // Set verbs (will throw if invalid) if (isset($route['verbs'])) { $routeMetaData->setVerbs($route['verbs']); } if (isset($route['collection'])) { $routeMetaData->setCollection($route['collection']); } // Add the route pattern $routeMetaData->setRoutePattern($route['routePattern']); if (isset($route['routeConditions']) && is_array($route['routeConditions'])) { $routeMetaData->setRouteConditions($route['routeConditions']); } // Set the exposure array if (isset($route['expose']) && is_array($route['expose'])) { $routeMetaData->setExpose($route['expose']); } // Set disable expose lookup if (isset($route['disableExpose'])) { $routeMetaData->setDisableExpose((bool) $route['disableExpose']); } // Set the allow options value if (isset($route['allowOptions'])) { $routeMetaData->setAllowedOptionRequest($route['allowOptions']); } // If the origin flag is set, set the name on the class meta data if (isset($route['origin']) && !is_null($route['origin'])) { if ($originFound) { throw DrestException::resourceCanOnlyHaveOneRouteSetAsOrigin(); } $metadata->originRouteName = $route['name']; $originFound = true; } $metadata->addRouteMetaData($routeMetaData); } }
/** * Get a route based on Entity::route_name. eg Entities\User::get_users * Syntax checking is performed * @param string $name * @param array $params * @throws DrestException on invalid syntax or unmatched named route * @return RouteMetaData $route */ protected function getNamedRoute($name, array $params = array()) { if (substr_count($name, '::') !== 1) { throw DrestException::invalidNamedRouteSyntax(); } $parts = explode('::', $name); // Allow exception to bubble up $classMetaData = $this->getClassMetadata($parts[0]); if (($route = $classMetaData->getRoutesMetaData($parts[1])) === false) { throw DrestException::unableToFindRouteByName($parts[1], $classMetaData->getClassName()); } $route->setRouteParams($params); return $route; }
/** * Ensures that this Configuration instance contains settings that are * suitable for a production environment. * * @throws DrestException If a configuration setting has a value that is not suitable for a production. */ public function ensureProductionSettings() { if ($this->inDebugMode()) { throw DrestException::currentlyRunningDebugMode(); } if (!$this->getMetadataCacheImpl()) { throw DrestException::metadataCacheNotConfigured(); } }
/** * Process the method * @param $resource * @param Mapping\ClassMetaData $metadata * @throws DrestException */ protected function processMethods($resource, Mapping\ClassMetaData $metadata) { /* @var \ReflectionMethod $method */ foreach ($resource['routes'] as $route) { // Make sure the for is not empty if (!isset($route['name']) || !is_string($route['name'])) { throw DrestException::handleForCannotBeEmpty(); } if (($routeMetaData = $metadata->getRouteMetaData($route['name'])) === false) { throw DrestException::handleAnnotationDoesntMatchRouteName($route['name']); } if ($routeMetaData->hasHandleCall()) { // There is already a handle set for this route throw DrestException::handleAlreadyDefinedForRoute($routeMetaData); } // Set the handle if (isset($route['handle_call'])) { $routeMetaData->setHandleCall($route['handle_call']); } } }
/** * Load metadata for a class name * @param object|string $class - Pass in either the class name, or an instance of that class * @return Mapping\ClassMetaData $metaData - return null if metadata couldn't be populated from annotations * @throws DrestException */ public function loadMetadataForClass($class) { $resourceFound = false; if (is_string($class)) { $class = new \ReflectionClass($class); } $metadata = new Mapping\ClassMetaData($class); foreach ($this->reader->getClassAnnotations($class) as $annotatedObject) { if ($annotatedObject instanceof Annotation\Resource) { $resourceFound = true; $originFound = false; if ($annotatedObject->routes === null) { throw DrestException::annotatedResourceRequiresAtLeastOneServiceDefinition($class->name); } $metadata->addRepresentations($annotatedObject->representations); foreach ($annotatedObject->routes as $route) { $routeMetaData = new Mapping\RouteMetaData(); // Set name $route->name = preg_replace("/[^a-zA-Z0-9_\\s]/", "", $route->name); if ($route->name == '') { throw DrestException::routeNameIsEmpty(); } if ($metadata->getRoutesMetaData($route->name) !== false) { throw DrestException::routeAlreadyDefinedWithName($class->name, $route->name); } $routeMetaData->setName($route->name); // Set verbs (will throw if invalid) if (isset($route->verbs)) { $routeMetaData->setVerbs($route->verbs); } if (isset($route->collection)) { $routeMetaData->setCollection($route->collection); } // Add the route pattern $routeMetaData->setRoutePattern($route->routePattern); if (is_array($route->routeConditions)) { $routeMetaData->setRouteConditions($route->routeConditions); } // Set the exposure array if (is_array($route->expose)) { $routeMetaData->setExpose($route->expose); } // Set the allow options value if (isset($route->allowOptions)) { $routeMetaData->setAllowedOptionRequest($route->allowOptions); } // Add action class if (isset($route->action)) { $routeMetaData->setActionClass($route->action); } // If the origin flag is set, set the name on the class meta data if (!is_null($route->origin)) { if ($originFound) { throw DrestException::resourceCanOnlyHaveOneRouteSetAsOrigin(); } $metadata->originRouteName = $route->name; $originFound = true; } $metadata->addRouteMetaData($routeMetaData); } // Set the handle calls foreach ($class->getMethods() as $method) { /* @var \ReflectionMethod $method */ if ($method->isPublic()) { foreach ($this->reader->getMethodAnnotations($method) as $methodAnnotation) { if ($methodAnnotation instanceof Annotation\Handle) { // Make sure the for is not empty if (empty($methodAnnotation->for) || !is_string($methodAnnotation->for)) { throw DrestException::handleForCannotBeEmpty(); } if (($routeMetaData = $metadata->getRoutesMetaData($methodAnnotation->for)) === false) { throw DrestException::handleAnnotationDoesntMatchRouteName($methodAnnotation->for); } if ($routeMetaData->hasHandleCall()) { // There is already a handle set for this route throw DrestException::alreadyHandleDefinedForRoute($routeMetaData); } $routeMetaData->setHandleCall($method->getName()); $routeMetaData->setInjectRequestIntoHandle($methodAnnotation->injectRequest); } } } } // Error for any push metadata routes that don't have a handle foreach ($metadata->getRoutesMetaData() as $routeMetaData) { /* @var RouteMetaData $routeMetaData */ if ($routeMetaData->needsHandleCall() && !$routeMetaData->hasHandleCall()) { throw DrestException::routeRequiresHandle($routeMetaData->getName()); } } } } return $resourceFound ? $metadata : null; }