Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSR-0 annotation reader, try #2 #154

Merged
merged 32 commits into from Jun 30, 2012
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ebf385a
Psr0Parser and assorted reflection classes.
chx Jun 16, 2012
43ac39b
Got the reflections working. The Psr0ClassReflection is complete, the…
chx Jun 18, 2012
c6668c1
Parse only on demand.
chx Jun 18, 2012
1b7334f
Code style fixes
chx Jun 18, 2012
d8d8c5f
Fix class hierarchy by extracting common code into a TokenParser class.
chx Jun 18, 2012
841e12c
Another code style fix.
chx Jun 18, 2012
2c12770
Ran php-cs-fixer.
chx Jun 18, 2012
bf4a344
More code style.
chx Jun 18, 2012
11f78d8
Lifted some code from Symfony to allow more flexibility in class plac…
chx Jun 19, 2012
e15f5cc
Handle parent class properly. Simplify brutally.
chx Jun 22, 2012
8b40c59
Added missing property doxygen.
chx Jun 22, 2012
cfc1f63
The first passing test.
chx Jun 22, 2012
8ec12f2
Tests only parse classes in Doctrine\\Tests so dont figure out where …
chx Jun 22, 2012
fb4ffbf
Added missing ReflectionMethod methods (not implemented)
chx Jun 22, 2012
a0ce5b9
And ReflectionProperty too.
chx Jun 22, 2012
fb21166
Fixed coding style issue: FALSE => false, TRUE => true but not in dox…
chx Jun 22, 2012
11c657b
Yay more doxygen.
chx Jun 22, 2012
7e117e4
Use dataProvider for testing instead.
chx Jun 22, 2012
db12028
Forgot to delete these.
chx Jun 22, 2012
9cf5ebd
Major restructure: renamed to StaticReflection, moved under Reflectio…
chx Jun 25, 2012
042829d
Test the parent class logic in StaticReflectionParser. Everything els…
chx Jun 25, 2012
b38f568
renamed parser to static.
chx Jun 25, 2012
a06231c
Using Symfony 2.1 findFile for simplicity and underscore support, ren…
chx Jun 25, 2012
fb1cb42
Added a reflection interface.
chx Jun 28, 2012
87ddc21
I really hate PSR-2.
chx Jun 28, 2012
9721284
more style fixes and renamed the new interface.
chx Jun 28, 2012
bad9f8e
added public to the interface.
chx Jun 28, 2012
adef493
Moved findFile to a class, added interface, renamed ns to namespace.
chx Jun 28, 2012
b8340e0
More code style.
chx Jun 28, 2012
823b0d1
More code style fixes thanks to the new cs fixer.
chx Jun 28, 2012
12cf440
false
chx Jun 28, 2012
b098c07
Opsie, forgot Psr0FindFile.php
chx Jun 30, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
145 changes: 11 additions & 134 deletions lib/Doctrine/Common/Annotations/PhpParser.php
Expand Up @@ -29,64 +29,45 @@
*/
final class PhpParser
{
/**
* The token list.
*
* @var array
*/
private $tokens;

/**
* The number of tokens.
*
* @var int
*/
private $numTokens = 0;

/**
* The current array pointer.
*
* @var int
*/
private $pointer = 0;

/**
* Parses a class.
*
* @param \ReflectionClass $class A <code>ReflectionClass</code> object.
* @return array A list with use statements in the form (Alias => FQN).
* @param \ReflectionClass $class A <code>ReflectionClass</code> object.
* @return array A list with use statements in the form (Alias => FQN).
*/
public function parseClass(\ReflectionClass $class)
{
if (method_exists($class, 'getUseStatements')) {
return $class->getUseStatements();
}

if (false === $filename = $class->getFilename()) {
return array();
}

$content = $this->getFileContent($filename, $class->getStartLine());
$namespace = str_replace('\\', '\\\\', $class->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$this->tokens = token_get_all('<?php ' . $content);
$this->numTokens = count($this->tokens);
$this->pointer = 0;
$tokenizer = new TokenParser('<?php ' . $content);

$statements = $this->parseUseStatements($class->getNamespaceName());
$statements = $tokenizer->parseUseStatements($class->getNamespaceName());

return $statements;
}

/**
* Get the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
* @return string The content of the file.
*/
private function getFileContent($filename, $lineNumber)
{
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while(!$file->eof()) {
while (!$file->eof()) {
if ($lineCnt++ == $lineNumber) {
break;
}
Expand All @@ -96,108 +77,4 @@ private function getFileContent($filename, $lineNumber)

return $content;
}

/**
* Gets the next non whitespace and non comment token.
*
* @return array The token if exists, null otherwise.
*/
private function next()
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if ($this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
$this->tokens[$i][0] === T_DOC_COMMENT) {

continue;
}

return $this->tokens[$i];
}

return null;
}

/**
* Get all use statements.
*
* @param string $namespaceName The namespace name of the reflected class.
* @return array A list with all found use statements.
*/
private function parseUseStatements($namespaceName)
{
$statements = array();
while (($token = $this->next())) {
if ($token[0] === T_USE) {
$statements = array_merge($statements, $this->parseUseStatement());
continue;
} else if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) {
continue;
}

// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
// for a previous namespace with the same name. This is the case if a namespace is defined twice
// or if a namespace with the same name is commented out.
$statements = array();
}

return $statements;
}

/**
* Get the namespace name.
*
* @return string The found namespace name.
*/
private function parseNamespace()
{
$namespace = '';
while (($token = $this->next())){
if ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR) {
$namespace .= $token[1];
} else {
break;
}
}

return $namespace;
}

/**
* Parse a single use statement.
*
* @return array A list with all found class names for a use statement.
*/
private function parseUseStatement()
{
$class = '';
$alias = '';
$statements = array();
$explicitAlias = false;
while (($token = $this->next())) {
$isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
} else if ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} else if ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} else if ($token === ',') {
$statements[strtolower($alias)] = $class;
$class = '';
$alias = '';
$explicitAlias = false;
} else if ($token === ';') {
$statements[strtolower($alias)] = $class;
break;
} else {
break;
}
}

return $statements;
}
}
175 changes: 175 additions & 0 deletions lib/Doctrine/Common/Annotations/TokenParser.php
@@ -0,0 +1,175 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace Doctrine\Common\Annotations;

/**
* Parses a file for namespaces/use/class declarations.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Kaps <christian.kaps@mohiva.com>
*/
class TokenParser
{
/**
* The token list.
*
* @var array
*/
private $tokens;

/**
* The number of tokens.
*
* @var int
*/
private $numTokens = 0;

/**
* The current array pointer.
*
* @var int
*/
private $pointer = 0;

public function __construct($contents)
{
$this->tokens = token_get_all($contents);
$this->numTokens = count($this->tokens);
$this->pointer = 0;
}

/**
* Gets the next non whitespace and non comment token.
*
* @param $docCommentIsComment
* If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
*
* @return array The token if exists, null otherwise.
*/
public function next($docCommentIsComment = TRUE)
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if ($this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) {

continue;
}

return $this->tokens[$i];
}

return null;
}

/**
* Parse a single use statement.
*
* @return array A list with all found class names for a use statement.
*/
public function parseUseStatement()
{
$class = '';
$alias = '';
$statements = array();
$explicitAlias = false;
while (($token = $this->next())) {
$isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
if (!$explicitAlias && $isNameToken) {
$class .= $token[1];
$alias = $token[1];
} else if ($explicitAlias && $isNameToken) {
$alias .= $token[1];
} else if ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} else if ($token === ',') {
$statements[strtolower($alias)] = $class;
$class = '';
$alias = '';
$explicitAlias = false;
} else if ($token === ';') {
$statements[strtolower($alias)] = $class;
break;
} else {
break;
}
}

return $statements;
}

/**
* Get all use statements.
*
* @param string $namespaceName The namespace name of the reflected class.
* @return array A list with all found use statements.
*/
public function parseUseStatements($namespaceName)
{
$statements = array();
while (($token = $this->next())) {
if ($token[0] === T_USE) {
$statements = array_merge($statements, $this->parseUseStatement());
continue;
}
if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) {
continue;
}

// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
// for a previous namespace with the same name. This is the case if a namespace is defined twice
// or if a namespace with the same name is commented out.
$statements = array();
}

return $statements;
}

/**
* Get the namespace.
*
* @return string The found namespace.
*/
public function parseNamespace()
{
$name = '';
while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) {
$name .= $token[1];
}

return $name;
}

/**
* Get the class name.
*
* @return string The foundclass name.
*/
public function parseClass()
{
// Namespaces and class names are tokenized the same: T_STRINGs
// separated by T_NS_SEPARATOR so we can use one function to provide
// both.
return $this->parseNamespace();
}
}
38 changes: 38 additions & 0 deletions lib/Doctrine/Common/Reflection/ClassFinderInterface.php
@@ -0,0 +1,38 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace Doctrine\Common\Reflection;

/**
* Finds a class in a PSR-0 structure.
*
* @author Karoly Negyesi <karoly@negyesi.net>
*/
interface ClassFinderInterface
{
/**
* Finds a class.
*
* @param string $class The name of the class.
*
* @return
* The name of the class or NULL if not found.
*/
public function findFile($class);
}