Skip to content

Commit 1a86eb1

Browse files
committedOct 22, 2019
wip
1 parent e01ecc0 commit 1a86eb1

File tree

8 files changed

+360
-30
lines changed

8 files changed

+360
-30
lines changed
 

‎composer.json

+5
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@
7474
"providers": [
7575
"Carbon\\Laravel\\ServiceProvider"
7676
]
77+
},
78+
"phpstan": {
79+
"includes": [
80+
"extension.neon"
81+
]
7782
}
7883
}
7984
}

‎extension.neon

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
-
3+
class: Carbon\PHPStan\MacroExtension
4+
tags:
5+
- phpstan.broker.methodsClassReflectionExtension

‎src/Carbon/PHPStan/Macro.php

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Carbon\PHPStan;
6+
7+
use Closure;
8+
use stdClass;
9+
use ErrorException;
10+
use ReflectionClass;
11+
use ReflectionFunction;
12+
use PHPStan\Reflection\Php\BuiltinMethodReflection;
13+
14+
final class Macro implements BuiltinMethodReflection
15+
{
16+
/**
17+
* The class name.
18+
*
19+
* @var string
20+
*/
21+
private $className;
22+
23+
/**
24+
* The method name.
25+
*
26+
* @var string
27+
*/
28+
private $methodName;
29+
30+
/**
31+
* The reflection function.
32+
*
33+
* @var ReflectionFunction
34+
*/
35+
private $reflectionFunction;
36+
37+
/**
38+
* The parameters.
39+
*
40+
* @var array
41+
*/
42+
private $parameters;
43+
44+
/**
45+
* The is static.
46+
*
47+
* @var bool
48+
*/
49+
private $isStatic = false;
50+
51+
/**
52+
* Macro constructor.
53+
*
54+
* @param string $className
55+
* @param string $methodName
56+
* @param ReflectionFunction $reflectionFunction
57+
*/
58+
public function __construct(string $className, string $methodName, ReflectionFunction $reflectionFunction)
59+
{
60+
$this->className = $className;
61+
$this->methodName = $methodName;
62+
$this->reflectionFunction = $reflectionFunction;
63+
$this->parameters = $this->reflectionFunction->getParameters();
64+
$this->isStatic = false;
65+
66+
if ($this->reflectionFunction->isClosure()) {
67+
try {
68+
/** @var Closure $closure */
69+
$closure = $this->reflectionFunction->getClosure();
70+
Closure::bind($closure, new stdClass);
71+
// The closure can be bound so it was not explicitly marked as static
72+
} catch (ErrorException $e) {
73+
// The closure was explicitly marked as static
74+
$this->isStatic = true;
75+
}
76+
}
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function getDeclaringClass(): ReflectionClass
83+
{
84+
return new ReflectionClass($this->className);
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function isPrivate(): bool
91+
{
92+
return false;
93+
}
94+
95+
/**
96+
* {@inheritdoc}
97+
*/
98+
public function isPublic(): bool
99+
{
100+
return true;
101+
}
102+
103+
public function isFinal(): bool
104+
{
105+
return false;
106+
}
107+
108+
public function isInternal(): bool
109+
{
110+
return false;
111+
}
112+
113+
public function isAbstract(): bool
114+
{
115+
return false;
116+
}
117+
118+
/**
119+
* {@inheritdoc}
120+
*/
121+
public function isStatic(): bool
122+
{
123+
return $this->isStatic;
124+
}
125+
126+
/**
127+
* Set the is static value.
128+
*
129+
* @param bool $isStatic
130+
*
131+
* @return void
132+
*/
133+
public function setIsStatic(bool $isStatic): void
134+
{
135+
$this->isStatic = $isStatic;
136+
}
137+
138+
/**
139+
* {@inheritdoc}
140+
*/
141+
public function getDocComment()
142+
{
143+
return $this->reflectionFunction->getDocComment();
144+
}
145+
146+
/**
147+
* {@inheritdoc}
148+
*/
149+
public function getFileName()
150+
{
151+
return $this->reflectionFunction->getFileName();
152+
}
153+
154+
/**
155+
* {@inheritdoc}
156+
*/
157+
public function getName(): string
158+
{
159+
return $this->methodName;
160+
}
161+
162+
/**
163+
* {@inheritdoc}
164+
*/
165+
public function getParameters(): array
166+
{
167+
return $this->parameters;
168+
}
169+
170+
/**
171+
* Set the parameters value.
172+
*
173+
* @param array $parameters
174+
*
175+
* @return void
176+
*/
177+
public function setParameters(array $parameters): void
178+
{
179+
$this->parameters = $parameters;
180+
}
181+
182+
/**
183+
* {@inheritdoc}
184+
*/
185+
public function getReturnType(): ?\ReflectionType
186+
{
187+
return $this->reflectionFunction->getReturnType();
188+
}
189+
190+
/**
191+
* {@inheritdoc}
192+
*/
193+
public function getStartLine()
194+
{
195+
return $this->reflectionFunction->getStartLine();
196+
}
197+
198+
/**
199+
* {@inheritdoc}
200+
*/
201+
public function getEndLine()
202+
{
203+
return $this->reflectionFunction->getEndLine();
204+
}
205+
206+
/**
207+
* {@inheritdoc}
208+
*/
209+
public function isDeprecated(): bool
210+
{
211+
return $this->reflectionFunction->isDeprecated();
212+
}
213+
214+
/**
215+
* {@inheritdoc}
216+
*/
217+
public function isVariadic(): bool
218+
{
219+
return $this->reflectionFunction->isVariadic();
220+
}
221+
222+
/**
223+
* {@inheritdoc}
224+
*/
225+
public function getPrototype(): BuiltinMethodReflection
226+
{
227+
return $this;
228+
}
229+
}

‎src/Carbon/PHPStan/MacroExtension.php

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Carbon\PHPStan;
4+
5+
use Carbon\CarbonInterface;
6+
use PHPStan\Reflection\ClassReflection;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\BrokerAwareExtension;
9+
use PHPStan\Reflection\Php\PhpMethodReflectionFactory;
10+
use PHPStan\Reflection\MethodsClassReflectionExtension;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class Extension implements MethodsClassReflectionExtension, BrokerAwareExtension
16+
{
17+
/**
18+
* @var \PHPStan\Reflection\Php\PhpMethodReflectionFactory
19+
*/
20+
protected $methodReflectionFactory;
21+
22+
/**
23+
* Extension constructor.
24+
*
25+
* @param \PHPStan\Reflection\Php\PhpMethodReflectionFactory $methodReflectionFactory
26+
*/
27+
public function __construct(PhpMethodReflectionFactory $methodReflectionFactory)
28+
{
29+
$this->methodReflectionFactory = $methodReflectionFactory;
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
36+
{
37+
if(!$classReflection->implements(CarbonInterface::class)) {
38+
/** @var CarbonInterface $class */
39+
$class = $classReflection->getName();
40+
41+
return $class::hasMacro($methodName);
42+
}
43+
44+
return false;
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
51+
{
52+
$property = $classReflection->getProperty('globalMacros');
53+
$property->setAccessible(true);
54+
$macro = $property->getValue()[$methodName];
55+
56+
return new Macro(
57+
$classReflection->getName(),
58+
$methodName,
59+
new \ReflectionFunction($macro)
60+
);
61+
}
62+
}

‎src/Carbon/Traits/Macro.php

+2-15
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,9 @@
1111
namespace Carbon\Traits;
1212

1313
/**
14-
* Trait Boundaries.
14+
* Trait Macros.
1515
*
16-
* startOf, endOf and derived method for each unit.
17-
*
18-
* Depends on the following properties:
19-
*
20-
* @property int $year
21-
* @property int $month
22-
* @property int $daysInMonth
23-
* @property int $quarter
24-
*
25-
* Depends on the following methods:
26-
*
27-
* @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0)
28-
* @method $this setDate(int $year, int $month, int $day)
29-
* @method $this addMonths(int $value = 1)
16+
* Allows users to register macros within the Carbon class.
3017
*/
3118
trait Macro
3219
{

‎src/Carbon/Traits/Mixin.php

+2-15
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,9 @@
1717
use Throwable;
1818

1919
/**
20-
* Trait Boundaries.
20+
* Trait Mixin.
2121
*
22-
* startOf, endOf and derived method for each unit.
23-
*
24-
* Depends on the following properties:
25-
*
26-
* @property int $year
27-
* @property int $month
28-
* @property int $daysInMonth
29-
* @property int $quarter
30-
*
31-
* Depends on the following methods:
32-
*
33-
* @method $this setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0)
34-
* @method $this setDate(int $year, int $month, int $day)
35-
* @method $this addMonths(int $value = 1)
22+
* Allows mixing in entire classes with multiple macros.
3623
*/
3724
trait Mixin
3825
{

‎tests/PHPStan/FeaturesTest.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
use Symfony\Component\Finder\Finder;
8+
use Symfony\Component\Console\Output\BufferedOutput;
9+
10+
class PHPStanTest extends AbstractTestCase
11+
{
12+
public function testAnalysesWithoutErrors(): void
13+
{
14+
if ($this->analyze(__DIR__ . '/Fixture.php') === 0) {
15+
$this->assertTrue(true);
16+
}
17+
}
18+
19+
private function analyze(string $file)
20+
{
21+
$output = shell_exec(
22+
'vendor/bin/phpstan'
23+
. ' --configuration ' . __DIR__ . '/../../extension.neon'
24+
. ' --no-progress'
25+
. ' --no-interaction'
26+
. ' ' . $file
27+
);
28+
29+
return $output;
30+
}
31+
}

‎tests/PHPStan/Fixture.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\PHPStan;
6+
7+
use Carbon\Carbon;
8+
9+
Carbon::macro('foo', static function (): string {
10+
return 'foo';
11+
});
12+
13+
class Fixture
14+
{
15+
public function testCarbonMacroCalledStatically(): string
16+
{
17+
return Carbon::foo();
18+
}
19+
20+
public function testCarbonMacroCalledDynamically(): string
21+
{
22+
return Carbon::now()->foo();
23+
}
24+
}

0 commit comments

Comments
 (0)
Please sign in to comment.