1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI;
9:
10: use Nette;
11: use Nette\Utils\Reflection;
12: use ReflectionClass;
13: use ReflectionMethod;
14:
15:
16: 17: 18:
19: class DependencyChecker
20: {
21: use Nette\SmartObject;
22:
23: const VERSION = 1;
24:
25:
26: private $dependencies = [];
27:
28:
29: 30: 31: 32:
33: public function add(array $deps)
34: {
35: $this->dependencies = array_merge($this->dependencies, $deps);
36: return $this;
37: }
38:
39:
40: 41: 42: 43:
44: public function export()
45: {
46: $files = $phpFiles = $classes = $functions = [];
47: foreach ($this->dependencies as $dep) {
48: if (is_string($dep)) {
49: $files[] = $dep;
50:
51: } elseif ($dep instanceof ReflectionClass) {
52: if (empty($classes[$name = $dep->getName()])) {
53: $all = [$name] + class_parents($name) + class_implements($name);
54: foreach ($all as &$item) {
55: $all += class_uses($item);
56: $phpFiles[] = (new ReflectionClass($item))->getFileName();
57: $classes[$item] = true;
58: }
59: }
60:
61: } elseif ($dep instanceof \ReflectionFunctionAbstract) {
62: $phpFiles[] = $dep->getFileName();
63: $functions[] = Reflection::toString($dep);
64:
65: } else {
66: throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep));
67: }
68: }
69:
70: $classes = array_keys($classes);
71: $functions = array_unique($functions, SORT_REGULAR);
72: $hash = self::calculateHash($classes, $functions);
73: $files = @array_map('filemtime', array_combine($files, $files));
74: $phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles));
75: return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash];
76: }
77:
78:
79: 80: 81: 82:
83: public static function isExpired($version, $files, &$phpFiles, $classes, $functions, $hash)
84: {
85: $current = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp));
86: $origPhpFiles = $phpFiles;
87: $phpFiles = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp));
88: return $version !== self::VERSION
89: || $files !== $current
90: || ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions));
91: }
92:
93:
94: private static function calculateHash($classes, $functions)
95: {
96: $hash = [];
97: foreach ($classes as $name) {
98: try {
99: $class = new ReflectionClass($name);
100: } catch (\ReflectionException $e) {
101: return;
102: }
103: $hash[] = [
104: $name,
105: Reflection::getUseStatements($class),
106: $class->isAbstract(),
107: get_parent_class($name),
108: class_implements($name),
109: class_uses($name),
110: ];
111:
112: foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
113: if ($prop->getDeclaringClass() == $class) {
114: $hash[] = [$name, $prop->getName(), $prop->getDocComment()];
115: }
116: }
117: foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
118: if ($method->getDeclaringClass() == $class) {
119: $hash[] = [
120: $name,
121: $method->getName(),
122: $method->getDocComment(),
123: self::hashParameters($method),
124: PHP_VERSION_ID >= 70000 && $method->hasReturnType()
125: ? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()]
126: : null,
127: ];
128: }
129: }
130: }
131:
132: $flip = array_flip($classes);
133: foreach ($functions as $name) {
134: try {
135: $method = strpos($name, '::') ? new ReflectionMethod($name) : new \ReflectionFunction($name);
136: } catch (\ReflectionException $e) {
137: return;
138: }
139: $class = $method instanceof ReflectionMethod ? $method->getDeclaringClass() : null;
140: if ($class && isset($flip[$class->getName()])) {
141: continue;
142: }
143: $hash[] = [
144: $name,
145: $class ? Reflection::getUseStatements($method->getDeclaringClass()) : null,
146: $method->getDocComment(),
147: self::hashParameters($method),
148: PHP_VERSION_ID >= 70000 && $method->hasReturnType()
149: ? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()]
150: : null,
151: ];
152: }
153:
154: return md5(serialize($hash));
155: }
156:
157:
158: private static function hashParameters(\ReflectionFunctionAbstract $method)
159: {
160: $res = [];
161: if (PHP_VERSION_ID < 70000 && $method->getNumberOfParameters() && $method->getFileName()) {
162: $res[] = file($method->getFileName())[$method->getStartLine() - 1];
163: }
164: foreach ($method->getParameters() as $param) {
165: $res[] = [
166: $param->getName(),
167: PHP_VERSION_ID >= 70000 ? [Reflection::getParameterType($param), $param->allowsNull()] : null,
168: $param->isVariadic(),
169: $param->isDefaultValueAvailable()
170: ? [Reflection::getParameterDefaultValue($param)]
171: : null,
172: ];
173: }
174: return $res;
175: }
176: }
177: