1: <?php
2:
3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Minion;
12:
13: use Knp\Provider\ConsoleServiceProvider;
14: use Minion\Service\Service;
15: use Minion\Service\ServiceConfig;
16: use Minion\Service\ServiceProviderInterface;
17: use Minion\Twig\AssetExtension;
18: use Minion\Twig\MiscExtension;
19: use Minion\Twig\TwigExtensionTagServiceProvider;
20: use Minion\Twig\UrlExtension;
21: use Propel\Runtime\Exception\InvalidArgumentException;
22: use Propel\Silex\PropelServiceProvider;
23: use Silex\Application as SilexApp;
24: use Silex\Provider\MonologServiceProvider;
25: use Silex\Provider\TwigServiceProvider;
26: use Silex\Provider\UrlGeneratorServiceProvider;
27: use Silex\ServiceProviderInterface as SilexServiceProviderInterface;
28: use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
29: use Symfony\Component\Config\FileLocator;
30: use Symfony\Component\Debug\Exception\FlattenException;
31: use Symfony\Component\Debug\ExceptionHandler;
32: use Symfony\Component\HttpFoundation\ParameterBag;
33: use Symfony\Component\HttpFoundation\Response;
34: use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
35: use Symfony\Component\HttpKernel\KernelEvents;
36: use Symfony\Component\Routing\Loader\YamlFileLoader;
37: use Symfony\Component\Routing\Route;
38: use Symfony\Component\Routing\RouteCollection;
39: use Symfony\Component\Yaml\Yaml;
40:
41: 42: 43: 44: 45: 46: 47: 48:
49: class Application extends SilexApp
50: {
51: const VERSION = '1.0.0';
52:
53:
54: protected $rootDir;
55:
56: protected $packageDir;
57:
58: protected $configPath;
59:
60: protected $propelConfigPath;
61:
62: protected $appNamespace;
63:
64: 65: 66: 67: 68:
69: public function getAppNamespace() {
70: return $this->appNamespace;
71: }
72:
73: 74: 75: 76: 77:
78: public function getRootDir() {
79: return $this->rootDir;
80: }
81:
82: 83: 84: 85: 86:
87: public function getPackageDir() {
88: return $this->packageDir;
89: }
90:
91: 92: 93: 94: 95:
96: public function getPropelConfigPath() {
97: return $this->propelConfigPath;
98: }
99:
100: 101: 102: 103: 104:
105: public function getConfigPath() {
106: return $this->configPath;
107: }
108:
109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123:
124: public function __construct($appNamespace = '', array $values = [], array $fixPaths = []) {
125:
126: try {
127: $defaultValues = [
128: 'environment' => 'prod',
129: 'minion.usePropel' => true,
130: 'minion.useTwig' => true,
131: ];
132:
133:
134: parent::__construct(\array_replace_recursive($defaultValues, $values));
135:
136:
137: $this->error(function(\Exception $ex, $code) {
138: return $this->minionError($ex, $code);
139: });
140:
141:
142: $this->resolvePaths($fixPaths);
143:
144:
145: $realNamespace = $appNamespace;
146: $jsonPath = Utils::fixPath($this->getRootDir() . '/composer.json');
147: if(!$appNamespace && \file_exists($jsonPath)) {
148: $json = \json_decode(\file_get_contents($jsonPath), true);
149: if(isset($json['autoload']) && \is_array($json['autoload']))
150: foreach($json['autoload'] as $psr)
151: if(\is_array($psr))
152: foreach($psr as $namespace => $path)
153: if(\preg_match('/^[\\/\\\\]?src[\\/\\\\]?/', $path))
154: $realNamespace = $namespace;
155: }
156: if(!$realNamespace)
157: throw new \InvalidArgumentException('Cannot resolve project namespace.');
158: $this->appNamespace = $realNamespace;
159:
160:
161: $this->register(new MonologServiceProvider(), [
162: 'monolog.logfile' => $this->getRootDir() . Utils::fixPath('/var/log/') .
163: ($this['debug'] ? 'dev.log' : 'prod.log'),
164: 'monolog.name' => $this->getAppNamespace(),
165: ]);
166:
167: if($this['minion.useTwig']) {
168: $this->register(new TwigServiceProvider(), [
169: 'twig.path' => $this->getRootDir() . Utils::fixPath('/src/Resources/views'),
170: 'twig.options' => [
171: 'cache' => $this->getRootDir() . Utils::fixPath('/var/cache/twig'),
172: ],
173: ]);
174:
175: $this['twig']->addExtension(new AssetExtension($this));
176: $this['twig']->addExtension(new UrlExtension($this));
177: $this['twig']->addExtension(new MiscExtension($this));
178: }
179:
180: $this->register(new UrlGeneratorServiceProvider());
181:
182: if($this['minion.usePropel']) {
183: $propelConfig = include Utils::fixPath($this->getPropelConfigPath());
184: if(isset($propelConfig['propel']['paths']['phpConfDir'])
185: && \file_exists($propelCompiledCfg = Utils::fixPath($propelConfig['propel']['paths']['phpConfDir']
186: . '/config.php'))
187: )
188: $this->register(new PropelServiceProvider(), [
189: 'propel.config_file' => $propelCompiledCfg,
190: ]);
191: else throw new InvalidArgumentException('Missing Propel compiled configuration file. Use "console propel:config:convert" in console');
192: }
193:
194: $this->register(new ConsoleServiceProvider(), [
195: 'console.name' => $this->getAppNamespace(),
196: 'console.version' => self::VERSION,
197: 'console.project_directory' => $this->getRootDir(),
198: ]
199: );
200:
201:
202: $loader = new YamlFileLoader(new FileLocator($this->getRootDir() . $this->getConfigPath()));
203:
204: $routes = $loader->load('routing.yml');
205:
206: if(\count($routes) > 0)
207: foreach($routes as $route)
208:
209: if($route->hasDefault('_controller'))
210: $route->setDefault('_controller', $this->getAppNamespace() . '\\Controller\\'
211: . $route->getDefault('_controller')
212: );
213: $this['routes']->addCollection($routes);
214:
215: $parametersFile = $this->getRootDir() . $this->getConfigPath() . 'parameters.yml';
216: if(!\file_exists($parametersFile))
217: throw new \RuntimeException('File parameters.yml does not exist or is not accessible');
218:
219: $parameters = Yaml::parse(\file_get_contents($parametersFile)) ?: [];
220: $this['parameters'] = new ParameterBag($parameters);
221:
222:
223: $userConfigFile = $this->getRootDir() . $this->getConfigPath() . 'config.yml';
224: if(\file_exists($userConfigFile)) {
225:
226: $userConfig = Yaml::parse(\file_get_contents($userConfigFile) ?: '') ?: [];
227: if(isset($userConfig['config']))
228: $this['config'] = $userConfig['config'];
229: if(isset($userConfig['services']))
230: 231: 232: 233:
234: foreach($userConfig['services'] as $id => $def) {
235: $serviceConfig = new ServiceConfig($id, $def);
236: $class = $serviceConfig->getProviderClass();
237:
238: if(\count($serviceConfig->getTags()) === 0) {
239:
240: $provider = new $class;
241: if($provider instanceof ServiceProviderInterface)
242: $provider->setServiceConfig($serviceConfig);
243: $this->register($provider, $serviceConfig->getOptions());
244: } else {
245: $calledTags = [];
246: foreach($serviceConfig->getTags() as $tag) {
247: if(\in_array($tag, $calledTags)) continue;
248: switch($tag) {
249: case 'twig.extension':
250: $calledTags[] = $tag;
251: if(!$this['minion.useTwig'])
252: throw new InvalidConfigurationException(
253: "Service '$id' uses 'twig.extension' tag when Twig Environment is not enabled");
254: $provider = new TwigExtensionTagServiceProvider();
255: $provider->setServiceConfig($serviceConfig);
256: $this->register($provider, $serviceConfig->getOptions());
257: break;
258: }
259: }
260: }
261: }
262: }
263:
264:
265:
266: $this['dispatcher']->addListener(KernelEvents::CONTROLLER, function(FilterControllerEvent $event) {
267:
268: $controller = $event->getController()[0];
269: $controller->setContainer($this);
270: });
271: } catch(\Exception $ex) {
272: if($this['environment'] == 'test')
273: throw $ex;
274: else
275: $this->fastAbort($ex);
276: }
277: }
278:
279: 280: 281: 282: 283: 284: 285: 286: 287: 288:
289: public function minionError(\Exception $ex, $code) {
290: $handler = new ExceptionHandler($this['debug']);
291: $exception = FlattenException::create($ex);
292: $response = Response::create($handler->getHtml($exception), $code, $exception->getHeaders())
293: ->setCharset(\ini_get('default_charset'))
294: ;
295:
296: if($this['debug'])
297: return $response;
298: else {
299: $content = <<<'HTML'
300: <!DOCTYPE html>
301: <html>
302: <head><title>Error %d</title></head>
303: <body><h1>Error %d occured</h1></body>
304: </html>
305: HTML;
306:
307: if($this['minion.useTwig']) {
308: $twig = $this['twig'];
309: $tpl = "Static/$code.html.twig";
310: if(!Utils::templateExists($twig, $tpl))
311: $content = \str_replace('%d', $code, $content);
312: else
313: $content = $twig->render($tpl, ['exception' => $ex]);
314: } elseif(\file_exists($tpl = Utils::fixPath($this->getRootDir() . "/Static/$code.html.php")))
315: $content = Utils::renderPhpTemplate($tpl, ['exception' => $ex]);
316:
317: $response->setStatusCode($code);
318: $response->setContent($content);
319:
320: return $response;
321: }
322: }
323:
324: 325: 326: 327: 328: 329: 330: 331: 332: 333:
334: public function fastAbort(\Exception $exception, $code = 500) {
335: $response = $this->minionError($exception, $code);
336: $response->send();
337: die;
338: }
339:
340: 341: 342: 343: 344: 345: 346: 347: 348:
349: protected function resolvePaths(array $fixPaths) {
350: $this->rootDir = \realpath(isset($fixPaths['rootDir']) ? $fixPaths['rootDir'] : __DIR__ . '/../../../../');
351: $this->packageDir = \realpath(isset($fixPaths['packageDir']) ? $fixPaths['packageDir'] : __DIR__ . '/../');
352: $this->configPath = Utils::fixPath(isset($fixPaths['configPath']) ? $fixPaths['configPath'] : '/app/');
353:
354: if($this->rootDir === false || $this->packageDir === false)
355: throw new \InvalidArgumentException('Bootstrap directories do not exists or are not accessible');
356:
357: if($this['minion.usePropel']) {
358: $this->propelConfigPath = \realpath(isset($fixPaths['propelConfigPath']) ? $fixPaths['propelConfigPath']
359: : Utils::fixPath($this->packageDir . '/propel.php')
360: );
361: if($this->propelConfigPath === false)
362: throw new \InvalidArgumentException('Propel configuration file in vendor Minion not found');
363: }
364: }
365: }