Skip to content

Commit e072b46

Browse files
authored
Merge pull request #25 from bc-luke/big-31502-super-reflection-cache-p
BIG-31502 Expand reflection caching
2 parents f69d60c + 1152b19 commit e072b46

22 files changed

Lines changed: 977 additions & 174 deletions

.phpstan/baseline.neon

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,11 @@ parameters:
9595
count: 1
9696
path: ../src/Reflection/ParameterInspector.php
9797

98-
-
99-
message: "#^Method Bigcommerce\\\\Injector\\\\Reflection\\\\ParameterInspector\\:\\:getMethodSignature\\(\\) return type has no value type specified in iterable type array\\.$#"
100-
count: 1
101-
path: ../src/Reflection/ParameterInspector.php
102-
103-
-
104-
message: "#^Method Bigcommerce\\\\Injector\\\\Reflection\\\\ParameterInspector\\:\\:getSignatureByClassName\\(\\) return type has no value type specified in iterable type array\\.$#"
105-
count: 1
106-
path: ../src/Reflection/ParameterInspector.php
107-
10898
-
10999
message: "#^Method Bigcommerce\\\\Injector\\\\Reflection\\\\ParameterInspector\\:\\:getSignatureByReflectionClass\\(\\) has parameter \\$reflectionClass with generic class ReflectionClass but does not specify its types\\: T$#"
110100
count: 1
111101
path: ../src/Reflection/ParameterInspector.php
112102

113-
-
114-
message: "#^Method Bigcommerce\\\\Injector\\\\Reflection\\\\ParameterInspector\\:\\:getSignatureByReflectionClass\\(\\) return type has no value type specified in iterable type array\\.$#"
115-
count: 1
116-
path: ../src/Reflection/ParameterInspector.php
117-
118103
-
119104
message: "#^Anonymous function has an unused use \\$className\\.$#"
120105
count: 1

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"minimum-stability": "dev",
2222
"prefer-stable": true,
2323
"require": {
24-
"php": "^7.3 || ^8.0",
24+
"php": "^8.1",
2525
"pimple/pimple": "*",
2626
"friendsofphp/proxy-manager-lts": "^1.0",
2727
"psr/container": "^1.0"

src/Cache/ArrayServiceCache.php

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
<?php
22
namespace Bigcommerce\Injector\Cache;
33

4+
use Countable;
5+
46
/**
57
* In process, memory array service cache.
8+
* @template T
9+
* @implements MultiGetCacheInterface<T>
610
*/
7-
class ArrayServiceCache implements ServiceCacheInterface
11+
class ArrayServiceCache implements ServiceCacheInterface, MultiGetCacheInterface, Countable
812
{
913
/**
10-
* @var array<string, string>
14+
* @var array<string, T>
1115
*/
12-
private $values = [];
16+
private array $values;
1317

1418
/**
15-
* @param array<string, string> $values
19+
* @param array<string, T> $values
1620
*/
17-
public function __construct($values = [])
21+
public function __construct(array $values = [])
1822
{
1923
$this->values = $values;
2024
}
@@ -23,9 +27,9 @@ public function __construct($values = [])
2327
* Retrieve the value of a key in the cache.
2428
*
2529
* @param string $key
26-
* @return string|false cached string value or false when key not present in a cache
30+
* @return T cached value or false when key not present in a cache
2731
*/
28-
public function get($key)
32+
public function get(string $key): mixed
2933
{
3034
if (!isset($this->values[$key])) {
3135
return false;
@@ -37,10 +41,10 @@ public function get($key)
3741
* Save a key/value pair to the cache.
3842
*
3943
* @param string $key
40-
* @param string $value
44+
* @param T $value
4145
* @return void
4246
*/
43-
public function set($key, $value)
47+
public function set(string $key, mixed $value): void
4448
{
4549
$this->values[$key] = $value;
4650
}
@@ -51,11 +55,42 @@ public function set($key, $value)
5155
* @param string $key
5256
* @return void
5357
*/
54-
public function remove($key)
58+
public function remove(string $key): void
5559
{
5660
if (!isset($this->values[$key])) {
5761
return;
5862
}
5963
unset($this->values[$key]);
6064
}
65+
66+
/**
67+
* Check if a key exists in the cache.
68+
*
69+
* @param string $key
70+
* @return bool
71+
*/
72+
public function has(string $key): bool
73+
{
74+
return isset($this->values[$key]);
75+
}
76+
77+
/**
78+
* Retrieve all entries of the cache.
79+
*
80+
* @return array<string, T>
81+
*/
82+
public function getAll(): array
83+
{
84+
return $this->values;
85+
}
86+
87+
/**
88+
* Count elements of the cache.
89+
*
90+
* @return int
91+
*/
92+
public function count(): int
93+
{
94+
return count($this->values);
95+
}
6196
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bigcommerce\Injector\Cache;
6+
7+
/**
8+
* @template T
9+
*/
10+
interface MultiGetCacheInterface
11+
{
12+
/**
13+
* Retrieve all entries of the cache.
14+
*
15+
* @return array<string, T>
16+
*/
17+
public function getAll(): array;
18+
}

src/Cache/NoOpServiceCache.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class NoOpServiceCache implements ServiceCacheInterface
1010
* Retrieve the value of a key in the cache.
1111
*
1212
* @param string $key
13-
* @return string|false cached string value or false when key not present in a cache
13+
* @return mixed|false cached string value or false when key not present in a cache
1414
*/
15-
public function get($key)
15+
public function get(string $key): mixed
1616
{
1717
return false;
1818
}
@@ -21,10 +21,10 @@ public function get($key)
2121
* Save a key/value pair to the cache.
2222
*
2323
* @param string $key
24-
* @param string $value
24+
* @param mixed $value
2525
* @return void
2626
*/
27-
public function set($key, $value)
27+
public function set(string $key, mixed $value): void
2828
{
2929
return;
3030
}
@@ -35,8 +35,19 @@ public function set($key, $value)
3535
* @param string $key
3636
* @return void
3737
*/
38-
public function remove($key)
38+
public function remove(string $key): void
3939
{
4040
return;
4141
}
42+
43+
/**
44+
* Check if a key exists in the cache.
45+
*
46+
* @param string $key
47+
* @return bool
48+
*/
49+
public function has(string $key): bool
50+
{
51+
return false;
52+
}
4253
}

src/Cache/ServiceCacheInterface.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface ServiceCacheInterface
1717
* @param string $key
1818
* @return mixed|false cached value or false when key not present in a cache
1919
*/
20-
public function get($key);
20+
public function get(string $key): mixed;
2121

2222
/**
2323
* Save a key/value pair to the cache.
@@ -26,13 +26,21 @@ public function get($key);
2626
* @param mixed $value
2727
* @return void
2828
*/
29-
public function set($key, $value);
29+
public function set(string $key, mixed $value);
30+
31+
/**
32+
* Check if a key exists in the cache.
33+
*
34+
* @param string $key
35+
* @return bool
36+
*/
37+
public function has(string $key): bool;
3038

3139
/**
3240
* Remove a key from the cache.
3341
*
3442
* @param string $key
3543
* @return void
3644
*/
37-
public function remove($key);
45+
public function remove(string $key): void;
3846
}

src/Injector.php

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
use Bigcommerce\Injector\Exception\InjectorInvocationException;
55
use Bigcommerce\Injector\Exception\MissingRequiredParameterException;
6-
use Bigcommerce\Injector\Reflection\ParameterInspector;
6+
use Bigcommerce\Injector\Reflection\ClassInspectorInterface;
7+
use InvalidArgumentException;
78
use Psr\Container\ContainerInterface;
9+
use ReflectionException;
810

911
/**
1012
* The Injector provides instantiation of objects (or invocation of methods) within the BC application and
@@ -26,11 +28,6 @@
2628
*/
2729
class Injector implements InjectorInterface
2830
{
29-
/**
30-
* @var ContainerInterface
31-
*/
32-
private $container;
33-
3431
/**
3532
* Regular Expressions matching dependencies that can be automatically created using their class name, even if they
3633
* are not defined in the IoC Container.
@@ -39,20 +36,8 @@ class Injector implements InjectorInterface
3936
*/
4037
protected $autoCreateWhiteList = [];
4138

42-
/**
43-
* @var ParameterInspector
44-
*/
45-
private $inspector;
46-
47-
/**
48-
*
49-
* @param ContainerInterface $container
50-
* @param ParameterInspector $inspector
51-
*/
52-
public function __construct(ContainerInterface $container, ParameterInspector $inspector)
39+
public function __construct(private ContainerInterface $container, private ClassInspectorInterface $classInspector)
5340
{
54-
$this->container = $container;
55-
$this->inspector = $inspector;
5641
}
5742

5843
/**
@@ -68,29 +53,27 @@ public function __construct(ContainerInterface $container, ParameterInspector $i
6853
* @param array $parameters An optional array of additional parameters to pass to the created objects constructor.
6954
* @return object
7055
* @throws InjectorInvocationException
71-
* @throws \InvalidArgumentException
72-
* @throws \ReflectionException
56+
* @throws InvalidArgumentException
57+
* @throws ReflectionException
7358
*/
7459
public function create($className, $parameters = [])
7560
{
76-
$reflectionClass = new \ReflectionClass($className);
77-
if (!$reflectionClass->hasMethod("__construct")) {
78-
//This class doesn't have a constructor
79-
return $reflectionClass->newInstanceWithoutConstructor();
61+
if (!$this->classInspector->classHasMethod($className, '__construct')) {
62+
return new $className();
8063
}
81-
if (!$reflectionClass->getMethod('__construct')->isPublic()) {
64+
65+
if (!$this->classInspector->methodIsPublic($className, '__construct')) {
8266
throw new InjectorInvocationException(
8367
"Injector failed to create $className - constructor isn't public." .
8468
" Do you need to use a static factory method instead?"
8569
);
8670
}
71+
8772
try {
88-
$parameters = $this->buildParameterArray(
89-
$this->inspector->getSignatureByReflectionClass($reflectionClass, "__construct"),
90-
$parameters
91-
);
73+
$signature = $this->classInspector->getMethodSignature($className, '__construct');
74+
$parameters = $this->buildParameterArray($signature, $parameters);
9275

93-
return $reflectionClass->newInstanceArgs($parameters);
76+
return new $className(...$parameters);
9477
} catch (MissingRequiredParameterException $e) {
9578
throw new InjectorInvocationException(
9679
"Can't create $className " .
@@ -122,19 +105,19 @@ public function create($className, $parameters = [])
122105
* @param array $parameters
123106
* @return mixed
124107
* @throws InjectorInvocationException
125-
* @throws \InvalidArgumentException
108+
* @throws InvalidArgumentException
126109
*/
127110
public function invoke($instance, $methodName, $parameters = [])
128111
{
129112
if (!is_object($instance)) {
130-
throw new \InvalidArgumentException(
113+
throw new InvalidArgumentException(
131114
"Attempted Injector::invoke on a non-object: " . gettype($instance) . "."
132115
);
133116
}
134117
$className = get_class($instance);
135118
try {
136119
$parameters = $this->buildParameterArray(
137-
$this->inspector->getSignatureByClassName($className, $methodName),
120+
$this->classInspector->getMethodSignature($className, $methodName),
138121
$parameters
139122
);
140123

@@ -145,7 +128,7 @@ public function invoke($instance, $methodName, $parameters = [])
145128
" - missing parameter '" . $e->getParameterString() . "'" .
146129
" could not be found. Either register it as a service or pass it to invoke via parameters."
147130
);
148-
} catch (\ReflectionException $e) {
131+
} catch (ReflectionException $e) {
149132
throw new InjectorInvocationException(
150133
"Failed to invoke $className::$methodName - method doesn't exist."
151134
);
@@ -197,8 +180,8 @@ public function canAutoCreate($className)
197180
* @return array
198181
* @throws InjectorInvocationException
199182
* @throws MissingRequiredParameterException
200-
* @throws \InvalidArgumentException
201-
* @throws \ReflectionException
183+
* @throws InvalidArgumentException
184+
* @throws ReflectionException
202185
*/
203186
private function buildParameterArray($methodSignature, $providedParameters)
204187
{

src/InjectorFactory.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bigcommerce\Injector;
6+
7+
use Bigcommerce\Injector\Cache\ArrayServiceCache;
8+
use Bigcommerce\Injector\Reflection\CachingClassInspector;
9+
use Bigcommerce\Injector\Reflection\ClassInspector;
10+
use Bigcommerce\Injector\Reflection\ClassInspectorStats;
11+
use Bigcommerce\Injector\Reflection\ParameterInspector;
12+
use Bigcommerce\Injector\Reflection\ReflectionClassCache;
13+
use Psr\Container\ContainerInterface;
14+
15+
class InjectorFactory
16+
{
17+
public static function create(ContainerInterface $container, int $reflectionClassCacheSize = 50): InjectorInterface
18+
{
19+
$classInspector = new ClassInspector(
20+
new ReflectionClassCache($reflectionClassCacheSize),
21+
new ParameterInspector(),
22+
new ClassInspectorStats(),
23+
);
24+
25+
$cachingClassInspector = new CachingClassInspector(
26+
$classInspector,
27+
new ArrayServiceCache(),
28+
);
29+
30+
return new Injector($container, $cachingClassInspector);
31+
}
32+
}

0 commit comments

Comments
 (0)