Skip to content

Commit a096ee8

Browse files
committed
implemented Fine Grained Dependencies
1 parent f9130f7 commit a096ee8

File tree

5 files changed

+116
-32
lines changed

5 files changed

+116
-32
lines changed

src/DI/Compiler.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,13 @@ public function getConfig()
123123

124124

125125
/**
126-
* Adds a files to the list of dependencies.
126+
* Adds dependencies to the list.
127+
* @param array of ReflectionClass|\ReflectionFunctionAbstract|string
127128
* @return self
128129
*/
129-
public function addDependencies(array $files)
130+
public function addDependencies(array $deps)
130131
{
131-
$this->dependencies->add($files);
132+
$this->dependencies->add(array_filter($deps));
132133
return $this;
133134
}
134135

src/DI/ContainerBuilder.php

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ContainerBuilder
4545
/** @var string[] of classes excluded from auto-wiring */
4646
private $excludedClasses = [];
4747

48-
/** @var array of file names */
48+
/** @var array */
4949
private $dependencies = [];
5050

5151
/** @var Nette\PhpGenerator\ClassType[] */
@@ -296,7 +296,7 @@ public function autowireArguments($class, $method, array $arguments)
296296
if (!$rm->isPublic()) {
297297
throw new ServiceCreationException("$class::$method() is not callable.");
298298
}
299-
$this->addDependency((string) $rm->getFileName());
299+
$this->addDependency($rm);
300300
return Helpers::autowireArguments($rm, $arguments, $this);
301301
}
302302

@@ -384,10 +384,6 @@ public function prepareClassList()
384384
}
385385
}
386386
}
387-
388-
foreach ($this->classList as $class => $foo) {
389-
$this->addDependency((string) (new ReflectionClass($class))->getFileName());
390-
}
391387
}
392388

393389

@@ -399,6 +395,7 @@ private function resolveImplement(ServiceDefinition $def, $name)
399395
}
400396
self::checkCase($interface);
401397
$rc = new ReflectionClass($interface);
398+
$this->addDependency($rc);
402399
$method = $rc->hasMethod('create')
403400
? $rc->getMethod('create')
404401
: ($rc->hasMethod('get') ? $rc->getMethod('get') : NULL);
@@ -474,13 +471,16 @@ private function resolveServiceClass($name, $recursive = [])
474471
$recursive[$name] = TRUE;
475472

476473
$def = $this->definitions[$name];
477-
$class = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities
478-
if ($class = $def->getClass() ?: $class) {
474+
$factoryClass = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities
475+
if ($class = $def->getClass() ?: $factoryClass) {
479476
$def->setClass($class);
480477
if (!class_exists($class) && !interface_exists($class)) {
481478
throw new ServiceCreationException("Type $class used in service '$name' not found or is not class or interface.");
482479
}
483480
self::checkCase($class);
481+
if (count($recursive) === 1) {
482+
$this->addDependency(new ReflectionClass($factoryClass ?: $class));
483+
}
484484

485485
} elseif ($def->getAutowired()) {
486486
trigger_error("Type of service '$name' is unknown.", E_USER_NOTICE);
@@ -517,6 +517,7 @@ private function resolveEntityClass($entity, $recursive = [])
517517
throw new ServiceCreationException(sprintf("Factory '%s' used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $name[0]));
518518
}
519519

520+
$this->addDependency($reflection);
520521
return PhpReflection::getReturnType($reflection);
521522

522523
} elseif ($service = $this->getServiceName($entity)) { // alias or factory
@@ -610,7 +611,7 @@ public function completeStatement(Statement $statement)
610611
$visibility = $rm->isProtected() ? 'protected' : 'private';
611612
throw new ServiceCreationException("Class $entity has $visibility constructor.");
612613
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
613-
$this->addDependency((string) $constructor->getFileName());
614+
$this->addDependency($constructor);
614615
$arguments = Helpers::autowireArguments($constructor, $arguments, $this);
615616
} elseif ($arguments) {
616617
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
@@ -630,7 +631,7 @@ public function completeStatement(Statement $statement)
630631
}
631632

632633
$rf = new \ReflectionFunction($entity[1]);
633-
$this->addDependency((string) $rf->getFileName());
634+
$this->addDependency($rf);
634635
$arguments = Helpers::autowireArguments($rf, $arguments, $this);
635636

636637
} else {
@@ -704,25 +705,25 @@ public function addExcludedClasses(array $classes)
704705

705706

706707
/**
707-
* Adds a file to the list of dependencies.
708+
* Adds item to the list of dependencies.
709+
* @param ReflectionClass|\ReflectionFunctionAbstract|string
708710
* @return self
709711
* @internal
710712
*/
711-
public function addDependency($file)
713+
public function addDependency($dep)
712714
{
713-
$this->dependencies[$file] = TRUE;
715+
$this->dependencies[] = $dep;
714716
return $this;
715717
}
716718

717719

718720
/**
719-
* Returns the list of dependent files.
721+
* Returns the list of dependencies.
720722
* @return array
721723
*/
722724
public function getDependencies()
723725
{
724-
unset($this->dependencies[FALSE]);
725-
return array_keys($this->dependencies);
726+
return $this->dependencies;
726727
}
727728

728729

src/DI/ContainerLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ private function isExpired($file)
100100
{
101101
if ($this->autoRebuild) {
102102
$meta = @unserialize(file_get_contents("$file.meta")); // @ - file may not exist
103-
return empty($meta[0]) || DependencyChecker::isExpired($meta);
103+
return empty($meta[0]) || DependencyChecker::isExpired(...$meta);
104104
}
105105
return FALSE;
106106
}

src/DI/DependencyChecker.php

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99

1010
use Nette;
1111
use ReflectionClass;
12+
use ReflectionMethod;
1213

1314

1415
/**
1516
* Cache dependencies checker.
1617
*/
1718
class DependencyChecker
1819
{
20+
const VERSION = 1;
21+
1922
use Nette\SmartObject;
2023

21-
/** @var array */
24+
/** @var array of ReflectionClass|\ReflectionFunctionAbstract|string */
2225
private $dependencies = [];
2326

2427

@@ -39,20 +42,99 @@ public function add(array $deps)
3942
*/
4043
public function export()
4144
{
42-
$files = array_filter($this->dependencies);
45+
$deps = array_unique($this->dependencies, SORT_REGULAR);
46+
$files = $phpFiles = $classes = $functions = [];
47+
foreach ($deps as $dep) {
48+
if (is_string($dep)) {
49+
$files[] = $dep;
50+
51+
} elseif ($dep instanceof ReflectionClass) {
52+
foreach (PhpReflection::getClassTree($dep) as $item) {
53+
$phpFiles[] = (new ReflectionClass($item))->getFileName();
54+
$classes[] = $item;
55+
}
56+
57+
} elseif ($dep instanceof \ReflectionFunctionAbstract) {
58+
$phpFiles[] = $dep->getFileName();
59+
$functions[] = $dep instanceof ReflectionMethod ? $dep->getDeclaringClass()->getName() . '::' . $dep->getName() : $dep->getName();
60+
61+
} else {
62+
throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep));
63+
}
64+
}
65+
66+
$classes = array_unique($classes);
67+
$functions = array_unique($functions, SORT_REGULAR);
68+
$hash = self::calculateHash($classes, $functions);
4369
$files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist
44-
return $files;
70+
$phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist
71+
return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash];
4572
}
4673

4774

4875
/**
4976
* Are dependencies expired?
5077
* @return bool
5178
*/
52-
public static function isExpired($files)
79+
public static function isExpired($version, $files, $phpFiles, $classes, $functions, $hash)
5380
{
5481
$current = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist
55-
return $files !== $current;
82+
$currentClass = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist
83+
return $version !== self::VERSION
84+
|| $files !== $current
85+
|| ($phpFiles !== $currentClass && $hash !== self::calculateHash($classes, $functions));
86+
}
87+
88+
89+
private static function calculateHash($classes, $functions)
90+
{
91+
$hash = [];
92+
foreach ($classes as $name) {
93+
try {
94+
$class = new ReflectionClass($name);
95+
} catch (\ReflectionException $e) {
96+
return;
97+
}
98+
$hash[] = [$name, PhpReflection::getUseStatements($class)];
99+
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
100+
if ($prop->getDeclaringClass() == $class) { // intentionally ==
101+
$hash[] = [$name, $prop->getName(), $prop->getDocComment()];
102+
}
103+
}
104+
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
105+
if ($method->getDeclaringClass() == $class) { // intentionally ==
106+
$hash[] = [
107+
$name,
108+
$method->getName(),
109+
$method->getDocComment(),
110+
implode('', $method->getParameters()),
111+
PHP_VERSION >= 70000 ? $method->getReturnType() : NULL
112+
];
113+
}
114+
}
115+
}
116+
117+
$flip = array_flip($classes);
118+
foreach ($functions as $name) {
119+
try {
120+
$method = strpos($name, '::') ? new ReflectionMethod($name) : new \ReflectionFunction($name);
121+
} catch (\ReflectionException $e) {
122+
return;
123+
}
124+
$class = $method instanceof ReflectionMethod ? $method->getDeclaringClass() : NULL;
125+
if ($class && isset($flip[$class->getName()])) {
126+
continue;
127+
}
128+
$hash[] = [
129+
$name,
130+
$class ? PhpReflection::getUseStatements($method->getDeclaringClass()) : NULL,
131+
$method->getDocComment(),
132+
implode('', $method->getParameters()),
133+
PHP_VERSION >= 70000 ? $method->getReturnType() : NULL
134+
];
135+
}
136+
137+
return md5(serialize($hash));
56138
}
57139

58140
}

tests/DI/Compiler.dependencies.phpt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,26 @@ require __DIR__ . '/../bootstrap.php';
1414
$compiler = new DI\Compiler;
1515

1616
Assert::same(
17-
[],
17+
[DependencyChecker::VERSION, [], [], [], [], '40cd750bba9870f18aada2478b24840a'],
1818
$compiler->exportDependencies()
1919
);
20-
Assert::false(DependencyChecker::isExpired($compiler->exportDependencies()));
20+
Assert::false(DependencyChecker::isExpired(...$compiler->exportDependencies()));
2121

2222

2323
$compiler->addDependencies(['file1', __FILE__]);
2424
Assert::same(
25-
['file1' => FALSE, __FILE__ => filemtime(__FILE__)],
25+
[DependencyChecker::VERSION, ['file1' => FALSE, __FILE__ => filemtime(__FILE__)], [], [], [], '40cd750bba9870f18aada2478b24840a'],
2626
$compiler->exportDependencies()
2727
);
28-
Assert::false(DependencyChecker::isExpired($compiler->exportDependencies()));
28+
Assert::false(DependencyChecker::isExpired(...$compiler->exportDependencies()));
2929

3030

3131
$compiler->addDependencies(['file1', NULL, 'file3']);
3232
Assert::same(
33-
['file1' => FALSE, __FILE__ => filemtime(__FILE__), 'file3' => FALSE],
33+
[DependencyChecker::VERSION, ['file1' => FALSE, __FILE__ => filemtime(__FILE__), 'file3' => FALSE], [], [], [], '40cd750bba9870f18aada2478b24840a'],
3434
$compiler->exportDependencies()
3535
);
3636

3737
$res = $compiler->exportDependencies();
38-
$res['file4'] = 123;
39-
Assert::true(DependencyChecker::isExpired($res));
38+
$res[1]['file4'] = 123;
39+
Assert::true(DependencyChecker::isExpired(...$res));

0 commit comments

Comments
 (0)