1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI;
9:
10: use Nette;
11: use Nette\Utils\Validators;
12: use Nette\Utils\Strings;
13: use Nette\PhpGenerator\Helpers as PhpHelpers;
14: use Nette\PhpGenerator\PhpLiteral;
15: use ReflectionClass;
16:
17:
18: 19: 20:
21: class PhpGenerator
22: {
23:
24: private $builder;
25:
26:
27: private $className;
28:
29:
30: private $generatedClasses = [];
31:
32:
33: private $currentService;
34:
35:
36: public function __construct(ContainerBuilder $builder)
37: {
38: $this->builder = $builder;
39: }
40:
41:
42: 43: 44: 45:
46: public function generate($className)
47: {
48: $this->builder->complete();
49:
50: $this->generatedClasses = [];
51: $this->className = $className;
52: $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className);
53: $containerClass->setExtends(Container::class);
54: $containerClass->addMethod('__construct')
55: ->addBody('$this->parameters = $params;')
56: ->addBody('$this->parameters += ?;', [$this->builder->parameters])
57: ->addParameter('params', [])
58: ->setTypeHint('array');
59:
60: $definitions = $this->builder->getDefinitions();
61: ksort($definitions);
62:
63: $meta = $containerClass->addProperty('meta')
64: ->setVisibility('protected')
65: ->setValue([Container::TYPES => $this->builder->getClassList()]);
66:
67: foreach ($definitions as $name => $def) {
68: $meta->value[Container::SERVICES][$name] = $def->getClass() ?: NULL;
69: foreach ($def->getTags() as $tag => $value) {
70: $meta->value[Container::TAGS][$tag][$name] = $value;
71: }
72: }
73:
74: foreach ($definitions as $name => $def) {
75: try {
76: $name = (string) $name;
77: $methodName = Container::getMethodName($name);
78: if (!PhpHelpers::isIdentifier($methodName)) {
79: throw new ServiceCreationException('Name contains invalid characters.');
80: }
81: $containerClass->addMethod($methodName)
82: ->addComment(PHP_VERSION_ID < 70000 ? '@return ' . ($def->getImplement() ?: $def->getClass()) : '')
83: ->setReturnType(PHP_VERSION_ID >= 70000 ? ($def->getImplement() ?: $def->getClass()) : NULL)
84: ->setBody($name === ContainerBuilder::THIS_CONTAINER ? 'return $this;' : $this->generateService($name))
85: ->setParameters($def->getImplement() ? [] : $this->convertParameters($def->parameters));
86: } catch (\Exception $e) {
87: throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
88: }
89: }
90:
91: $aliases = $this->builder->getAliases();
92: ksort($aliases);
93: $meta->value[Container::ALIASES] = $aliases;
94:
95: return $this->generatedClasses;
96: }
97:
98:
99: 100: 101: 102:
103: private function generateService($name)
104: {
105: $def = $this->builder->getDefinition($name);
106:
107: if ($def->isDynamic()) {
108: return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);',
109: ["Unable to create dynamic service '$name', it must be added using addService()"]
110: );
111: }
112:
113: $entity = $def->getFactory()->getEntity();
114: $serviceRef = $this->builder->getServiceName($entity);
115: $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
116: ? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef])
117: : $def->getFactory();
118:
119: $this->currentService = NULL;
120: $code = '$service = ' . $this->formatStatement($factory) . ";\n";
121:
122: if (($class = $def->getClass()) && !$serviceRef && $class !== $entity
123: && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $class))
124: ) {
125: $code .= PhpHelpers::formatArgs("if (!\$service instanceof $class) {\n"
126: . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
127: ["Unable to create service '$name', value returned by factory is not $class type."]
128: );
129: }
130:
131: $this->currentService = $name;
132: foreach ($def->getSetup() as $setup) {
133: $code .= $this->formatStatement($setup) . ";\n";
134: }
135:
136: $code .= 'return $service;';
137:
138: if (!$def->getImplement()) {
139: return $code;
140: }
141:
142: $factoryClass = (new Nette\PhpGenerator\ClassType)
143: ->addImplement($def->getImplement());
144:
145: $factoryClass->addProperty('container')
146: ->setVisibility('private');
147:
148: $factoryClass->addMethod('__construct')
149: ->addBody('$this->container = $container;')
150: ->addParameter('container')
151: ->setTypeHint($this->className);
152:
153: $factoryClass->addMethod($def->getImplementMode())
154: ->setParameters($this->convertParameters($def->parameters))
155: ->setBody(str_replace('$this', '$this->container', $code))
156: ->setReturnType(PHP_VERSION_ID >= 70000 ? $def->getClass() : NULL);
157:
158: if (PHP_VERSION_ID < 70000) {
159: $this->generatedClasses[] = $factoryClass;
160: $factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}"));
161: return "return new {$factoryClass->getName()}(\$this);";
162: }
163:
164: return 'return new class ($this) ' . $factoryClass . ';';
165: }
166:
167:
168: 169: 170: 171:
172: private function formatStatement(Statement $statement)
173: {
174: $entity = $statement->getEntity();
175: $arguments = $statement->arguments;
176:
177: if (is_string($entity) && Strings::contains($entity, '?')) {
178: return $this->formatPhp($entity, $arguments);
179:
180: } elseif ($service = $this->builder->getServiceName($entity)) {
181: return $this->formatPhp('$this->?(?*)', [Container::getMethodName($service), $arguments]);
182:
183: } elseif ($entity === 'not') {
184: return $this->formatPhp('!?', [$arguments[0]]);
185:
186: } elseif (is_string($entity)) {
187: return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), $arguments ? [$arguments] : []);
188:
189: } elseif ($entity[0] === '') {
190: return $this->formatPhp("$entity[1](?*)", [$arguments]);
191:
192: } elseif ($entity[0] instanceof Statement) {
193: $inner = $this->formatPhp('?', [$entity[0]]);
194: if (substr($inner, 0, 4) === 'new ') {
195: $inner = "($inner)";
196: }
197: return $this->formatPhp("$inner->?(?*)", [$entity[1], $arguments]);
198:
199: } elseif ($entity[1][0] === '$') {
200: $name = substr($entity[1], 1);
201: if ($append = (substr($name, -2) === '[]')) {
202: $name = substr($name, 0, -2);
203: }
204: if ($this->builder->getServiceName($entity[0])) {
205: $prop = $this->formatPhp('?->?', [$entity[0], $name]);
206: } else {
207: $prop = $this->formatPhp($entity[0] . '::$?', [$name]);
208: }
209: return $arguments
210: ? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]])
211: : $prop;
212:
213: } elseif ($service = $this->builder->getServiceName($entity[0])) {
214: return $this->formatPhp('?->?(?*)', [$entity[0], $entity[1], $arguments]);
215:
216: } else {
217: return $this->formatPhp("$entity[0]::$entity[1](?*)", [$arguments]);
218: }
219: }
220:
221:
222: 223: 224: 225: 226:
227: public function formatPhp($statement, $args)
228: {
229: array_walk_recursive($args, function (&$val) {
230: if ($val instanceof Statement) {
231: $val = new PhpLiteral($this->formatStatement($val));
232:
233: } elseif (is_string($val) && substr($val, 0, 2) === '@@') {
234: $val = substr($val, 1);
235:
236: } elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) {
237: $name = substr($val, 1);
238: if ($name === ContainerBuilder::THIS_CONTAINER) {
239: $val = new PhpLiteral('$this');
240: } elseif ($name === $this->currentService) {
241: $val = new PhpLiteral('$service');
242: } else {
243: $val = new PhpLiteral($this->formatStatement(new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$name])));
244: }
245: }
246: });
247: return PhpHelpers::formatArgs($statement, $args);
248: }
249:
250:
251: 252: 253: 254:
255: private function convertParameters(array $parameters)
256: {
257: $res = [];
258: foreach ($parameters as $k => $v) {
259: $tmp = explode(' ', is_int($k) ? $v : $k);
260: $param = $res[] = new Nette\PhpGenerator\Parameter(end($tmp));
261: if (!is_int($k)) {
262: $param->setOptional(TRUE)->setDefaultValue($v);
263: }
264: if (isset($tmp[1])) {
265: $param->setTypeHint($tmp[0]);
266: }
267: }
268: return $res;
269: }
270:
271: }
272: