/** * Registers services on the given app. * * @param Application $app An Application instance. * * @throws Exception\MissingDependencyException if the application does not have a descriptor.builder service. * @throws Exception\MissingDependencyException if the application does not have a serializer service. */ public function register(Application $app) { if (!isset($app['descriptor.builder'])) { throw new Exception\MissingDependencyException('The builder object that is used to construct the ProjectDescriptor is missing'); } if (!isset($app['serializer'])) { throw new Exception\MissingDependencyException('The serializer object that is used to read the template configuration with is missing'); } // parameters $app['linker.substitutions'] = array('phpDocumentor\\Descriptor\\ProjectDescriptor' => array('files'), 'phpDocumentor\\Descriptor\\FileDescriptor' => array('tags', 'classes', 'interfaces', 'traits', 'functions', 'constants'), 'phpDocumentor\\Descriptor\\ClassDescriptor' => array('tags', 'parent', 'interfaces', 'constants', 'properties', 'methods', 'usedTraits'), 'phpDocumentor\\Descriptor\\InterfaceDescriptor' => array('tags', 'parent', 'constants', 'methods'), 'phpDocumentor\\Descriptor\\TraitDescriptor' => array('tags', 'properties', 'methods', 'usedTraits'), 'phpDocumentor\\Descriptor\\FunctionDescriptor' => array('tags', 'arguments'), 'phpDocumentor\\Descriptor\\MethodDescriptor' => array('tags', 'arguments'), 'phpDocumentor\\Descriptor\\ArgumentDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\PropertyDescriptor' => array('tags', 'types'), 'phpDocumentor\\Descriptor\\ConstantDescriptor' => array('tags', 'types'), 'phpDocumentor\\Descriptor\\Tag\\ParamDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\Tag\\ReturnDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\Tag\\SeeDescriptor' => array('reference'), 'phpDocumentor\\Descriptor\\Type\\CollectionDescriptor' => array('baseType', 'types', 'keyTypes')); // services $app['compiler'] = $app->share(function ($container) { $compiler = new Compiler(); $compiler->insert(new ElementsIndexBuilder(), ElementsIndexBuilder::COMPILER_PRIORITY); $compiler->insert(new MarkerFromTagsExtractor(), MarkerFromTagsExtractor::COMPILER_PRIORITY); $compiler->insert(new ExampleTagsEnricher($container['parser.example.finder']), ExampleTagsEnricher::COMPILER_PRIORITY); $compiler->insert(new PackageTreeBuilder(), PackageTreeBuilder::COMPILER_PRIORITY); $compiler->insert(new NamespaceTreeBuilder(), NamespaceTreeBuilder::COMPILER_PRIORITY); $compiler->insert(new ResolveInlineLinkAndSeeTags($container['transformer.routing.queue']), ResolveInlineLinkAndSeeTags::COMPILER_PRIORITY); $compiler->insert($container['linker'], Linker::COMPILER_PRIORITY); $compiler->insert($container['transformer'], Transformer::COMPILER_PRIORITY); $compiler->insert(new Debug($container['monolog'], $container['descriptor.analyzer']), Debug::COMPILER_PRIORITY); return $compiler; }); $app['linker'] = $app->share(function ($app) { return new Linker($app['linker.substitutions']); }); $app['transformer.behaviour.collection'] = $app->share(function () { return new Behaviour\Collection(); }); $app['transformer.routing.standard'] = $app->share(function ($container) { /** @var ProjectDescriptorBuilder $projectDescriptorBuilder */ $projectDescriptorBuilder = $container['descriptor.builder']; return new Router\StandardRouter($projectDescriptorBuilder); }); $app['transformer.routing.external'] = $app->share(function ($container) { return new Router\ExternalRouter($container['config']); }); $app['transformer.routing.queue'] = $app->share(function ($container) { $queue = new Router\Queue(); // TODO: load from app configuration instead of hardcoded $queue->insert($container['transformer.routing.external'], 10500); $queue->insert($container['transformer.routing.standard'], 10000); return $queue; }); $app['transformer.writer.collection'] = $app->share(function ($container) { return new Writer\Collection($container['transformer.routing.queue']); }); $this->provideTemplatingSystem($app); $app['transformer'] = $app->share(function ($container) { $transformer = new Transformer($container['transformer.template.collection'], $container['transformer.writer.collection']); $transformer->setBehaviours($container['transformer.behaviour.collection']); return $transformer; }); $app->command(new TransformCommand($app['descriptor.builder'], $app['transformer'], $app['compiler'])); $app->command(new ListCommand($app['transformer.template.factory'])); }
/** * @covers \phpDocumentor\Transformer\Router\Queue::match */ public function testNullIsReturnedWhenNoMatchingRuleCanBeFound() { $nodeName = 'test'; $this->fixture->insert($this->givenARouterMatchingNodeWithResult($nodeName, false), 0); // Act $result = $this->fixture->match($nodeName); // Assert $this->assertNull($result); }
/** * Registers services on the given app. * * @param Application $app An Application instance */ public function register(Application $app) { if (!isset($app['descriptor.builder'])) { throw new Exception\MissingDependencyException('The builder object that is used to construct the ProjectDescriptor is missing'); } if (!isset($app['serializer'])) { throw new Exception\MissingDependencyException('The serializer object that is used to read the template configuration with is missing'); } $templateDir = __DIR__ . '/../../../data/templates'; // vendored installation if (file_exists(__DIR__ . '/../../../../templates')) { $templateDir = __DIR__ . '/../../../../templates'; } // parameters $app['transformer.template.location'] = $templateDir; $app['linker.substitutions'] = array('phpDocumentor\\Descriptor\\ProjectDescriptor' => array('files'), 'phpDocumentor\\Descriptor\\FileDescriptor' => array('tags', 'classes', 'interfaces', 'traits'), 'phpDocumentor\\Descriptor\\ClassDescriptor' => array('tags', 'parent', 'interfaces', 'constants', 'properties', 'methods'), 'phpDocumentor\\Descriptor\\InterfaceDescriptor' => array('tags', 'parent', 'constants', 'methods'), 'phpDocumentor\\Descriptor\\TraitDescriptor' => array('tags', 'properties', 'methods'), 'phpDocumentor\\Descriptor\\MethodDescriptor' => array('tags', 'arguments'), 'phpDocumentor\\Descriptor\\ArgumentDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\PropertyDescriptor' => array('tags', 'types'), 'phpDocumentor\\Descriptor\\ConstantDescriptor' => array('tags', 'types'), 'phpDocumentor\\Descriptor\\Tag\\ParamDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\Tag\\ReturnDescriptor' => array('types'), 'phpDocumentor\\Descriptor\\Tag\\SeeDescriptor' => array('reference')); // services $app['compiler'] = $app->share(function ($container) { $compiler = new Compiler(); $compiler->insert(new ElementsIndexBuilder(), ElementsIndexBuilder::COMPILER_PRIORITY); $compiler->insert(new PackageTreeBuilder(), PackageTreeBuilder::COMPILER_PRIORITY); $compiler->insert(new NamespaceTreeBuilder(), NamespaceTreeBuilder::COMPILER_PRIORITY); $compiler->insert($container['linker'], Linker::COMPILER_PRIORITY); $compiler->insert($container['transformer'], Transformer::COMPILER_PRIORITY); $compiler->insert(new Debug($container['monolog'], $container['descriptor.analyzer']), Debug::COMPILER_PRIORITY); return $compiler; }); $app['linker'] = $app->share(function ($app) { return new Linker($app['linker.substitutions']); }); $app['transformer.behaviour.collection'] = $app->share(function () { return new Behaviour\Collection(); }); $app['transformer.routing.queue'] = $app->share(function () { $queue = new Router\Queue(); // TODO: load from app configuration instead of hardcoded $queue->insert(new Router\StandardRouter(), 10000); return $queue; }); $app['transformer.writer.collection'] = $app->share(function ($container) { return new Writer\Collection($container['transformer.routing.queue']); }); $app['transformer.template.collection'] = $app->share(function ($container) { return new Template\Collection($container['transformer.template.location'], $container['serializer']); }); $app['transformer'] = $app->share(function ($container) { $transformer = new Transformer($container['transformer.template.collection'], $container['transformer.writer.collection']); $transformer->setBehaviours($container['transformer.behaviour.collection']); return $transformer; }); $app->command(new TransformCommand($app['descriptor.builder'], $app['transformer'], $app['compiler'])); $app->command(new ListCommand()); }
/** * Uses the currently selected node and transformation to assemble the destination path for the file. * * The Twig writer accepts the use of a Query to be able to generate output for multiple objects using the same * template. * * The given node is the result of such a query, or if no query given the selected element, and the transformation * contains the destination file. * * Since it is important to be able to generate a unique name per element can the user provide a template variable * in the name of the file. * Such a template variable always resides between double braces and tries to take the node value of a given * query string. * * Example: * * An artefact stating `classes/{{name}}.html` will try to find the * node 'name' as a child of the given $node and use that value instead. * * @param DescriptorAbstract $node * @param Transformation $transformation * * @throws \InvalidArgumentException if no artifact is provided and no routing rule matches. * * @return string|false returns the destination location or false if generation should be aborted. */ protected function getDestinationPath($node, Transformation $transformation) { $writer = $this; if (!$node) { throw new \UnexpectedValueException('The transformation node in the twig writer is not expected to be false or null'); } if (!$transformation->getArtifact()) { $rule = $this->routers->match($node); if (!$rule) { throw new \InvalidArgumentException('No matching routing rule could be found for the given node, please provide an artifact location, ' . 'encountered: ' . ($node === null ? 'NULL' : get_class($node))); } $rule = new ForFileProxy($rule); $url = $rule->generate($node); if ($url === false || $url[0] !== DIRECTORY_SEPARATOR) { return false; } $path = $transformation->getTransformer()->getTarget() . str_replace('/', DIRECTORY_SEPARATOR, $url); } else { $path = $transformation->getTransformer()->getTarget() . DIRECTORY_SEPARATOR . $transformation->getArtifact(); } $destination = preg_replace_callback('/{{([^}]+)}}/u', function ($query) use($node, $writer) { // strip any surrounding \ or / return trim((string) $writer->walkObjectTree($node, $query[1]), '\\/'); }, $path); // replace any \ with the directory separator to be compatible with the // current filesystem and allow the next file_exists to do its work $destination = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $destination); // create directory if it does not exist yet if (!file_exists(dirname($destination))) { mkdir(dirname($destination), 0777, true); } return $destination; }
/** * Returns a relative URL from the webroot if the given FQSEN exists in the project. * * Example usage inside template would be (where @link is an attribute called link): * * ``` * <xsl:value-of select="php:function('phpDocumentor\Plugin\Core\Xslt\Extension::path', string(@link))" /> * ``` * * @param string $fqsen * * @return bool|string */ public static function path($fqsen) { $projectDescriptor = self::$descriptorBuilder->getProjectDescriptor(); $elementList = $projectDescriptor->getIndexes()->get('elements'); $node = $fqsen; if (isset($elementList[$fqsen])) { $node = $elementList[$fqsen]; } elseif (isset($elementList['~\\' . $fqsen])) { $node = $elementList['~\\' . $fqsen]; } $rule = self::$routers->match($node); if (!$rule) { return ''; } $generatedUrl = $rule->generate($node); return $generatedUrl ? ltrim($generatedUrl, '/') : false; }
/** * @param ProjectDescriptor $project * @param $element * @return false|string */ private function generateUrlForXmlElement(ProjectDescriptor $project, $element) { $elements = $project->getIndexes()->get('elements'); $elementFqcn = ($element->parentNode->nodeName === 'namespace' ? '~\\' : '') . $element->nodeValue; $node = isset($elements[$elementFqcn]) ? $elements[$elementFqcn] : $element->nodeValue; // do not use the normalized version if the element is not found! $rule = $this->routers->match($node); if (!$rule) { throw new \InvalidArgumentException('No matching routing rule could be found for the given node, please provide an artifact location, ' . 'encountered: ' . ($node === null ? 'NULL' : get_class($node))); } $rule = new ForFileProxy($rule); $url = $rule->generate($node); return $url; }
/** * Converts the given path to be relative to the root of the documentation * target directory. * * It is not possible to use absolute paths in documentation templates since * they may be used locally, or in a subfolder. As such we need to calculate * the number of levels to go up from the current document's directory and * then append the given path. * * For example: * * Suppose you are in <root>/classes/my/class.html and you want open * <root>/my/index.html then you provide 'my/index.html' to this method * and it will convert it into ../../my/index.html (<root>/classes/my is * two nesting levels until the root). * * This method does not try to normalize or optimize the paths in order to * save on development time and performance, and because it adds no real * value. * * @param string $relative_path * * @return string */ public function convertToRootPath($relative_path) { // get the path to the root directory $path_parts = explode(DIRECTORY_SEPARATOR, $this->getDestination()); if (count($path_parts) > 1) { $path_to_root = implode('/', array_fill(0, count($path_parts) - 1, '..')) . '/'; } else { $path_to_root = ''; } if (is_string($relative_path) && $relative_path[0] != '@') { // append the relative path to the root return $path_to_root . ltrim($relative_path, '/'); } $rule = $this->routers->match($relative_path); if (!$rule) { return null; } $generatedPath = $rule->generate($relative_path); return $generatedPath ? $path_to_root . ltrim($generatedPath, '/') : null; }
/** * Uses the currently selected node and transformation to assemble the destination path for the file. * * The Twig writer accepts the use of a Query to be able to generate output for multiple objects using the same * template. * * The given node is the result of such a query, or if no query given the selected element, and the transformation * contains the destination file. * * Since it is important to be able to generate a unique name per element can the user provide a template variable * in the name of the file. * Such a template variable always resides between double braces and tries to take the node value of a given * query string. * * Example: * * An artifact stating `classes/{{name}}.html` will try to find the * node 'name' as a child of the given $node and use that value instead. * * @param DescriptorAbstract $node * @param Transformation $transformation * * @throws \InvalidArgumentException if no artifact is provided and no routing rule matches. * @throws \UnexpectedValueException if the provided node does not contain anything. * * @return string|false returns the destination location or false if generation should be aborted. */ protected function getDestinationPath($node, Transformation $transformation) { $writer = $this; if (!$node) { throw new \UnexpectedValueException('The transformation node in the twig writer is not expected to be false or null'); } if (!$transformation->getArtifact()) { $rule = $this->routers->match($node); if (!$rule) { throw new \InvalidArgumentException('No matching routing rule could be found for the given node, please provide an artifact location, ' . 'encountered: ' . ($node === null ? 'NULL' : get_class($node))); } $rule = new ForFileProxy($rule); $url = $rule->generate($node); if ($url === false || $url[0] !== DIRECTORY_SEPARATOR) { return false; } $path = $transformation->getTransformer()->getTarget() . str_replace('/', DIRECTORY_SEPARATOR, $url); } else { $path = $transformation->getTransformer()->getTarget() . DIRECTORY_SEPARATOR . $transformation->getArtifact(); } $finder = new Pathfinder(); $destination = preg_replace_callback('/{{([^}]+)}}/', function ($query) use($node, $writer, $finder) { // strip any surrounding \ or / $filepart = trim((string) current($finder->find($node, $query[1])), '\\/'); // make it windows proof if (extension_loaded('iconv')) { $filepart = iconv('UTF-8', 'ASCII//TRANSLIT', $filepart); } $filepart = strpos($filepart, '/') !== false ? implode('/', array_map('urlencode', explode('/', $filepart))) : implode('\\', array_map('urlencode', explode('\\', $filepart))); return $filepart; }, $path); var_dump($destination); // replace any \ with the directory separator to be compatible with the // current filesystem and allow the next file_exists to do its work $destination = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $destination); // create directory if it does not exist yet if (!file_exists(dirname($destination))) { mkdir(dirname($destination), 0777, true); } return $destination; }
protected function renderLink($path, $presentation) { $url = false; $rule = $this->routers->match($path); if ($rule) { $generatedUrl = $rule->generate($path); $url = $generatedUrl ? ltrim($generatedUrl, '/') : false; } if (is_string($url) && $url[0] != '/' && strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { $url = $this->convertToRootPath($url); } switch ($presentation) { case 'url': // return the first url return $url; case 'class:short': $parts = explode('\\', $path); $path = end($parts); break; } return $url ? sprintf('<a href="%s">%s</a>', $url, $path) : $path; }