1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Bridges\ApplicationDI;
9:
10: use Composer\Autoload\ClassLoader;
11: use Nette;
12: use Nette\Application\UI;
13: use Tracy;
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: ->setFactory(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->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)
113: ->addTag('nette.presenter', $def->getClass());
114:
115: if (is_subclass_of($def->getClass(), UI\Presenter::class)) {
116: $def->addSetup('$invalidLinkMode', [$this->invalidLinkMode]);
117: }
118: }
119: }
120:
121:
122:
123: private function findPresenters()
124: {
125: $config = $this->getConfig();
126: $classes = [];
127:
128: if ($config['scanDirs']) {
129: if (!class_exists(Nette\Loaders\RobotLoader::class)) {
130: throw new Nette\NotSupportedException("RobotLoader is required to find presenters, install package `nette/robot-loader` or disable option {$this->prefix('scanDirs')}: false");
131: }
132: $robot = new Nette\Loaders\RobotLoader;
133: $robot->addDirectory($config['scanDirs']);
134: $robot->acceptFiles = ['*' . $config['scanFilter'] . '*.php'];
135: $robot->rebuild();
136: $classes = array_keys($robot->getIndexedClasses());
137: $this->getContainerBuilder()->addDependency($this->tempFile);
138: }
139:
140: if ($config['scanComposer']) {
141: $rc = new \ReflectionClass(ClassLoader::class);
142: $classFile = dirname($rc->getFileName()) . '/autoload_classmap.php';
143: if (is_file($classFile)) {
144: $this->getContainerBuilder()->addDependency($classFile);
145: $classes = array_merge($classes, array_keys(call_user_func(function ($path) {
146: return require $path;
147: }, $classFile)));
148: }
149: }
150:
151: $presenters = [];
152: foreach (array_unique($classes) as $class) {
153: if (
154: strpos($class, $config['scanFilter']) !== false
155: && class_exists($class)
156: && ($rc = new \ReflectionClass($class))
157: && $rc->implementsInterface(Nette\Application\IPresenter::class)
158: && !$rc->isAbstract()
159: ) {
160: $presenters[] = $rc->getName();
161: }
162: }
163: return $presenters;
164: }
165: }
166: