1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Bridges\ApplicationDI;
9:
10: use Nette;
11: use Nette\Application\UI;
12: use Tracy;
13: use Composer\Autoload\ClassLoader;
14:
15:
16: 17: 18:
19: class ApplicationExtension extends Nette\DI\CompilerExtension
20: {
21: public $defaults = [
22: 'debugger' => NULL,
23: 'errorPresenter' => 'Nette:Error',
24: 'catchExceptions' => NULL,
25: 'mapping' => NULL,
26: 'scanDirs' => [],
27: 'scanComposer' => NULL,
28: 'scanFilter' => 'Presenter',
29: 'silentLinks' => FALSE,
30: ];
31:
32:
33: private $debugMode;
34:
35:
36: private $invalidLinkMode;
37:
38:
39: private $tempFile;
40:
41:
42: public function __construct($debugMode = FALSE, array $scanDirs = NULL, $tempDir = NULL)
43: {
44: $this->defaults['debugger'] = interface_exists(Tracy\IBarPanel::class);
45: $this->defaults['scanDirs'] = (array) $scanDirs;
46: $this->defaults['scanComposer'] = class_exists(ClassLoader::class);
47: $this->defaults['catchExceptions'] = !$debugMode;
48: $this->debugMode = $debugMode;
49: $this->tempFile = $tempDir ? $tempDir . '/' . urlencode(__CLASS__) : NULL;
50: }
51:
52:
53: public function loadConfiguration()
54: {
55: $config = $this->validateConfig($this->defaults);
56: $builder = $this->getContainerBuilder();
57: $builder->addExcludedClasses([UI\Presenter::class]);
58:
59: $this->invalidLinkMode = $this->debugMode
60: ? UI\Presenter::INVALID_LINK_TEXTUAL | ($config['silentLinks'] ? 0 : UI\Presenter::INVALID_LINK_WARNING)
61: : UI\Presenter::INVALID_LINK_WARNING;
62:
63: $application = $builder->addDefinition($this->prefix('application'))
64: ->setClass(Nette\Application\Application::class)
65: ->addSetup('$catchExceptions', [$config['catchExceptions']])
66: ->addSetup('$errorPresenter', [$config['errorPresenter']]);
67:
68: if ($config['debugger']) {
69: $application->addSetup('Nette\Bridges\ApplicationTracy\RoutingPanel::initializePanel');
70: }
71:
72: $touch = $this->debugMode && $config['scanDirs'] ? $this->tempFile : NULL;
73: $presenterFactory = $builder->addDefinition($this->prefix('presenterFactory'))
74: ->setClass(Nette\Application\IPresenterFactory::class)
75: ->setFactory(Nette\Application\PresenterFactory::class, [new Nette\DI\Statement(
76: Nette\Bridges\ApplicationDI\PresenterFactoryCallback::class, [1 => $this->invalidLinkMode, $touch]
77: )]);
78:
79: if ($config['mapping']) {
80: $presenterFactory->addSetup('setMapping', [$config['mapping']]);
81: }
82:
83: $builder->addDefinition($this->prefix('linkGenerator'))
84: ->setFactory(Nette\Application\LinkGenerator::class, [
85: 1 => new Nette\DI\Statement('@Nette\Http\IRequest::getUrl'),
86: ]);
87:
88: if ($this->name === 'application') {
89: $builder->addAlias('application', $this->prefix('application'));
90: $builder->addAlias('nette.presenterFactory', $this->prefix('presenterFactory'));
91: }
92: }
93:
94:
95: public function beforeCompile()
96: {
97: $builder = $this->getContainerBuilder();
98: $all = [];
99:
100: foreach ($builder->findByType(Nette\Application\IPresenter::class) as $def) {
101: $all[$def->getClass()] = $def;
102: }
103:
104: $counter = 0;
105: foreach ($this->findPresenters() as $class) {
106: if (empty($all[$class])) {
107: $all[$class] = $builder->addDefinition($this->prefix(++$counter))->setClass($class);
108: }
109: }
110:
111: foreach ($all as $def) {
112: $def->setInject(TRUE)->addTag('nette.presenter', $def->getClass());
113: if (is_subclass_of($def->getClass(), UI\Presenter::class)) {
114: $def->addSetup('$invalidLinkMode', [$this->invalidLinkMode]);
115: }
116: }
117: }
118:
119:
120:
121: private function findPresenters()
122: {
123: $config = $this->getConfig();
124: $classes = [];
125:
126: if ($config['scanDirs']) {
127: if (!class_exists(Nette\Loaders\RobotLoader::class)) {
128: throw new Nette\NotSupportedException("RobotLoader is required to find presenters, install package `nette/robot-loader` or disable option {$this->prefix('scanDirs')}: false");
129: }
130: $robot = new Nette\Loaders\RobotLoader;
131: $robot->setCacheStorage(new Nette\Caching\Storages\DevNullStorage);
132: $robot->addDirectory($config['scanDirs']);
133: $robot->acceptFiles = '*' . $config['scanFilter'] . '*.php';
134: $robot->rebuild();
135: $classes = array_keys($robot->getIndexedClasses());
136: $this->getContainerBuilder()->addDependency($this->tempFile);
137: }
138:
139: if ($config['scanComposer']) {
140: $rc = new \ReflectionClass(ClassLoader::class);
141: $classFile = dirname($rc->getFileName()) . '/autoload_classmap.php';
142: if (is_file($classFile)) {
143: $this->getContainerBuilder()->addDependency($classFile);
144: $classes = array_merge($classes, array_keys(call_user_func(function ($path) {
145: return require $path;
146: }, $classFile)));
147: }
148: }
149:
150: $presenters = [];
151: foreach (array_unique($classes) as $class) {
152: if (strpos($class, $config['scanFilter']) !== FALSE && class_exists($class)
153: && ($rc = new \ReflectionClass($class)) && $rc->implementsInterface(Nette\Application\IPresenter::class)
154: && !$rc->isAbstract()
155: ) {
156: $presenters[] = $rc->getName();
157: }
158: }
159: return $presenters;
160: }
161:
162: }
163: