Skip to content

Commit 44c268b

Browse files
committed
Committed code from PHP-DI
1 parent 902686b commit 44c268b

17 files changed

+532
-0
lines changed

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# .gitattributes
2+
tests/ export-ignore
3+
.travis.yml export-ignore
4+
5+
# Auto detect text files and perform LF normalization
6+
* text=auto

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
.idea/*
3+
vendor/*
4+
composer.phar
5+
composer.lock

.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: php
2+
3+
php:
4+
- 5.3
5+
- 5.4
6+
- 5.5
7+
8+
before_script:
9+
- mkdir -p build/logs
10+
- composer require satooshi/php-coveralls:dev-master --dev --no-progress --prefer-source
11+
12+
script:
13+
- phpunit --coverage-clover build/logs/clover.xml
14+
15+
after_script:
16+
- php vendor/bin/coveralls -v

LICENSE

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Copyright (C) 2012 Matthieu Napoli
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4+
associated documentation files (the "Software"), to deal in the Software without restriction,
5+
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
6+
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
7+
subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all copies or substantial
10+
portions of the Software.
11+
12+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
14+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
15+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
16+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# PhpDocReader
2+
3+
This project is a sub-project of [PHP-DI](http://mnapoli.github.io/PHP-DI/).

composer.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "mnapoli/phpdocreader",
3+
"type": "library",
4+
"keywords": ["phpdoc", "reflection"],
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-0": {
8+
"PhpDocReader": "src/",
9+
"UnitTest": "tests/"
10+
}
11+
},
12+
"require": {
13+
"php": ">=5.3.0",
14+
"doctrine/annotations": "1.*"
15+
}
16+
}

phpunit.xml.dist

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<phpunit backupGlobals="false"
3+
backupStaticAttributes="false"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
processIsolation="false"
9+
stopOnFailure="false"
10+
syntaxCheck="false"
11+
bootstrap="./tests/bootstrap.php">
12+
13+
<testsuites>
14+
<testsuite name="PhpDocReader tests">
15+
<directory>./tests/UnitTest/PhpDocReader/</directory>
16+
</testsuite>
17+
</testsuites>
18+
19+
</phpunit>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace PhpDocReader;
4+
5+
/**
6+
* @author Matthieu Napoli <matthieu@mnapoli.fr>
7+
*/
8+
class AnnotationException extends \Exception
9+
{
10+
}

src/PhpDocReader/PhpDocReader.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
namespace PhpDocReader;
4+
5+
use Doctrine\Common\Annotations\PhpParser;
6+
use ReflectionClass;
7+
use ReflectionMethod;
8+
use ReflectionParameter;
9+
use ReflectionProperty;
10+
11+
/**
12+
* PhpDoc reader
13+
*
14+
* @author Matthieu Napoli <matthieu@mnapoli.fr>
15+
*/
16+
class PhpDocReader
17+
{
18+
/**
19+
* @var PhpParser
20+
*/
21+
private $phpParser;
22+
23+
24+
/**
25+
* Constructor
26+
*/
27+
public function __construct()
28+
{
29+
$this->phpParser = new PhpParser();
30+
}
31+
32+
/**
33+
* Parse the docblock of the property to get the var annotation
34+
* @param ReflectionClass $class
35+
* @param ReflectionProperty $property
36+
* @throws AnnotationException
37+
* @return string|null Type of the property (content of var annotation)
38+
*/
39+
public function getPropertyType(ReflectionClass $class, ReflectionProperty $property)
40+
{
41+
// Get the content of the @var annotation
42+
if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
43+
list(, $type) = $matches;
44+
} else {
45+
return null;
46+
}
47+
48+
// If the class name is not fully qualified (i.e. doesn't start with a \)
49+
if ($type[0] !== '\\') {
50+
$alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos);
51+
$loweredAlias = strtolower($alias);
52+
53+
// Retrieve "use" statements
54+
$uses = $this->phpParser->parseClass($property->getDeclaringClass());
55+
56+
$found = false;
57+
58+
if (isset($uses[$loweredAlias])) {
59+
// Imported classes
60+
if (false !== $pos) {
61+
$type = $uses[$loweredAlias] . substr($type, $pos);
62+
} else {
63+
$type = $uses[$loweredAlias];
64+
}
65+
$found = true;
66+
} elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
67+
$type = $class->getNamespaceName() . '\\' . $type;
68+
$found = true;
69+
} elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
70+
// Class namespace
71+
$type = $uses['__NAMESPACE__'] . '\\' . $type;
72+
$found = true;
73+
} elseif ($this->classExists($type)) {
74+
// No namespace
75+
$found = true;
76+
}
77+
78+
if (!$found) {
79+
throw new AnnotationException("The @var annotation on {$class->name}::" . $property->getName()
80+
. " contains a non existent class. Did you maybe forget to add a 'use' statement for this annotation?");
81+
}
82+
}
83+
84+
if (!$this->classExists($type)) {
85+
throw new AnnotationException("The @var annotation on {$class->name}::" . $property->getName()
86+
. " contains a non existent class");
87+
}
88+
89+
// Remove the leading \ (FQN shouldn't contain it)
90+
$type = ltrim($type, '\\');
91+
92+
return $type;
93+
}
94+
95+
/**
96+
* Parse the docblock of the property to get the param annotation
97+
* @param ReflectionClass $class
98+
* @param ReflectionMethod $method
99+
* @throws AnnotationException
100+
* @return string|null Type of the property (content of var annotation)
101+
*/
102+
public function getParameterType(ReflectionClass $class, ReflectionMethod $method, ReflectionParameter $parameter)
103+
{
104+
// Use reflection
105+
$parameterClass = $parameter->getClass();
106+
if ($parameterClass !== null) {
107+
return $parameterClass->name;
108+
}
109+
110+
$parameterName = $parameter->name;
111+
// Get the content of the @param annotation
112+
if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) {
113+
list(, $type) = $matches;
114+
} else {
115+
return null;
116+
}
117+
118+
// If the class name is not fully qualified (i.e. doesn't start with a \)
119+
if ($type[0] !== '\\') {
120+
$alias = (false === $pos = strpos($type, '\\')) ? $type : substr($type, 0, $pos);
121+
$loweredAlias = strtolower($alias);
122+
123+
// Retrieve "use" statements
124+
$uses = $this->phpParser->parseClass($method->getDeclaringClass());
125+
126+
$found = false;
127+
128+
if (isset($uses[$loweredAlias])) {
129+
// Imported classes
130+
if (false !== $pos) {
131+
$type = $uses[$loweredAlias] . substr($type, $pos);
132+
} else {
133+
$type = $uses[$loweredAlias];
134+
}
135+
$found = true;
136+
} elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
137+
$type = $class->getNamespaceName() . '\\' . $type;
138+
$found = true;
139+
} elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
140+
// Class namespace
141+
$type = $uses['__NAMESPACE__'] . '\\' . $type;
142+
$found = true;
143+
} elseif ($this->classExists($type)) {
144+
// No namespace
145+
$found = true;
146+
}
147+
148+
if (!$found) {
149+
throw new AnnotationException("The @param annotation for parameter $parameterName of {$class->name}::" . $method->name
150+
. " contains a non existent class. Did you maybe forget to add a 'use' statement for this annotation?");
151+
}
152+
}
153+
154+
if (!$this->classExists($type)) {
155+
throw new AnnotationException("The @param annotation for parameter $parameterName of {$class->name}::" . $method->name
156+
. " contains a non existent class");
157+
}
158+
159+
// Remove the leading \ (FQN shouldn't contain it)
160+
$type = ltrim($type, '\\');
161+
162+
return $type;
163+
}
164+
165+
/**
166+
* @param string $class
167+
* @return bool
168+
*/
169+
private function classExists($class)
170+
{
171+
return class_exists($class) || interface_exists($class);
172+
}
173+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace UnitTest\PhpDocReader\Fixtures;
4+
5+
class SomeDependencyFixture
6+
{
7+
8+
}

0 commit comments

Comments
 (0)