+++ /dev/null
-<?php
-
-/*
- * This file is part of the Behat.
- * (c) Konstantin Kudryashov <ever.zet@gmail.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Behat\Behat\Context\Reader;
-
-use Behat\Behat\Context\Annotation\AnnotationReader;
-use Behat\Behat\Context\Environment\ContextEnvironment;
-use Behat\Testwork\Call\Callee;
-use ReflectionClass;
-use ReflectionException;
-use ReflectionMethod;
-
-/**
- * Reads context callees by annotations using registered annotation readers.
- *
- * @author Konstantin Kudryashov <ever.zet@gmail.com>
- */
-final class AnnotatedContextReader implements ContextReader
-{
- const DOCLINE_TRIMMER_REGEX = '/^\/\*\*\s*|^\s*\*\s*|\s*\*\/$|\s*$/';
-
- /**
- * @var string[]
- */
- private static $ignoreAnnotations = array(
- '@param',
- '@return',
- '@throws',
- '@see',
- '@uses',
- '@todo'
- );
- /**
- * @var AnnotationReader[]
- */
- private $readers = array();
-
- /**
- * Registers annotation reader.
- *
- * @param AnnotationReader $reader
- */
- public function registerAnnotationReader(AnnotationReader $reader)
- {
- $this->readers[] = $reader;
- }
-
- /**
- * {@inheritdoc}
- */
- public function readContextCallees(ContextEnvironment $environment, $contextClass)
- {
- $reflection = new ReflectionClass($contextClass);
-
- $callees = array();
- foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
- foreach ($this->readMethodCallees($reflection->getName(), $method) as $callee) {
- $callees[] = $callee;
- }
- }
-
- return $callees;
- }
-
- /**
- * Loads callees associated with specific method.
- *
- * @param string $class
- * @param ReflectionMethod $method
- *
- * @return Callee[]
- */
- private function readMethodCallees($class, ReflectionMethod $method)
- {
- $callees = array();
-
- // read parent annotations
- try {
- $prototype = $method->getPrototype();
- // error occurs on every second PHP stable release - getPrototype() returns itself
- if ($prototype->getDeclaringClass()->getName() !== $method->getDeclaringClass()->getName()) {
- $callees = array_merge($callees, $this->readMethodCallees($class, $prototype));
- }
- } catch (ReflectionException $e) {
- }
-
- if ($docBlock = $method->getDocComment()) {
- $callees = array_merge($callees, $this->readDocBlockCallees($class, $method, $docBlock));
- }
-
- return $callees;
- }
-
- /**
- * Reads callees from the method doc block.
- *
- * @param string $class
- * @param ReflectionMethod $method
- * @param string $docBlock
- *
- * @return Callee[]
- */
- private function readDocBlockCallees($class, ReflectionMethod $method, $docBlock)
- {
- $callees = array();
- $description = $this->readDescription($docBlock);
- $docBlock = $this->mergeMultilines($docBlock);
-
- foreach (explode("\n", $docBlock) as $docLine) {
- $docLine = preg_replace(self::DOCLINE_TRIMMER_REGEX, '', $docLine);
-
- if ($this->isEmpty($docLine)) {
- continue;
- }
-
- if ($this->isNotAnnotation($docLine)) {
- continue;
- }
-
- if ($callee = $this->readDocLineCallee($class, $method, $docLine, $description)) {
- $callees[] = $callee;
- }
- }
-
- return $callees;
- }
-
- /**
- * Merges multiline strings (strings ending with "\")
- *
- * @param string $docBlock
- *
- * @return string
- */
- private function mergeMultilines($docBlock)
- {
- return preg_replace("#\\\\$\s*+\*\s*+([^\\\\$]++)#m", '$1', $docBlock);
- }
-
- /**
- * Extracts a description from the provided docblock,
- * with support for multiline descriptions.
- *
- * @param string $docBlock
- *
- * @return string
- */
- private function readDescription($docBlock)
- {
- // Remove indentation
- $description = preg_replace('/^[\s\t]*/m', '', $docBlock);
-
- // Remove block comment syntax
- $description = preg_replace('/^\/\*\*\s*|^\s*\*\s|^\s*\*\/$/m', '', $description);
-
- // Remove annotations
- $description = preg_replace('/^@.*$/m', '', $description);
-
- // Ignore docs after a "--" separator
- if (preg_match('/^--.*$/m', $description)) {
- $descriptionParts = preg_split('/^--.*$/m', $description);
- $description = array_shift($descriptionParts);
- }
-
- // Trim leading and trailing newlines
- $description = trim($description, "\r\n");
-
- return $description;
- }
-
- /**
- * Checks if provided doc lien is empty.
- *
- * @param string $docLine
- *
- * @return Boolean
- */
- private function isEmpty($docLine)
- {
- return '' == $docLine;
- }
-
- /**
- * Checks if provided doc line is not an annotation.
- *
- * @param string $docLine
- *
- * @return Boolean
- */
- private function isNotAnnotation($docLine)
- {
- return '@' !== substr($docLine, 0, 1);
- }
-
- /**
- * Reads callee from provided doc line using registered annotation readers.
- *
- * @param string $class
- * @param ReflectionMethod $method
- * @param string $docLine
- * @param null|string $description
- *
- * @return null|Callee
- */
- private function readDocLineCallee($class, ReflectionMethod $method, $docLine, $description = null)
- {
- if ($this->isIgnoredAnnotation($docLine)) {
- return null;
- }
-
- foreach ($this->readers as $reader) {
- if ($callee = $reader->readCallee($class, $method, $docLine, $description)) {
- return $callee;
- }
- }
-
- return null;
- }
-
- /**
- * Checks if provided doc line is one of the ignored annotations.
- *
- * @param string $docLine
- *
- * @return Boolean
- */
- private function isIgnoredAnnotation($docLine)
- {
- $lowDocLine = strtolower($docLine);
- foreach (self::$ignoreAnnotations as $ignoredAnnotation) {
- if ($ignoredAnnotation == substr($lowDocLine, 0, strlen($ignoredAnnotation))) {
- return true;
- }
- }
-
- return false;
- }
-}