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