Overview

Namespaces

  • Minion
    • Service
    • Twig

Classes

  • Minion\Application
  • Minion\Console
  • Minion\Controller
  • Minion\Service\ServiceConfig
  • Minion\Service\ServiceProvider
  • Minion\Twig\AssetExtension
  • Minion\Twig\MiscExtension
  • Minion\Twig\TwigExtensionTagServiceProvider
  • Minion\Twig\UrlExtension
  • Minion\Utils

Interfaces

  • Minion\ControllerInterface
  • Minion\Service\ServiceConfigInterface
  • Minion\Service\ServiceProviderInterface
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Minion package.
  5:  * For the full copyright and license information, please view the LICENSE
  6:  * file that was distributed with this source code.
  7:  *
  8:  * @license MIT License
  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:  * Class Application.
 43:  *
 44:  * Wrapper for Silex framework to improve it's flexibility and usability.
 45:  *
 46:  * @package Minion
 47:  * @author Damian SzczerbiƄski <dszczer@gmail.com>
 48:  */
 49: class Application extends SilexApp
 50: {
 51:     const VERSION = '1.0.0';
 52: 
 53:     /** @var string Project root directory */
 54:     protected $rootDir;
 55:     /** @var string Package directory */
 56:     protected $packageDir;
 57:     /** @var string Project configuration path */
 58:     protected $configPath;
 59:     /** @var string Propel configuration file */
 60:     protected $propelConfigPath;
 61:     /** @var string Application namespace */
 62:     protected $appNamespace;
 63: 
 64:     /**
 65:      * Get application namespace.
 66:      *
 67:      * @return string
 68:      */
 69:     public function getAppNamespace() {
 70:         return $this->appNamespace;
 71:     }
 72: 
 73:     /**
 74:      * Get project root directory.
 75:      *
 76:      * @return string
 77:      */
 78:     public function getRootDir() {
 79:         return $this->rootDir;
 80:     }
 81: 
 82:     /**
 83:      * get Minion package directory.
 84:      *
 85:      * @return string
 86:      */
 87:     public function getPackageDir() {
 88:         return $this->packageDir;
 89:     }
 90: 
 91:     /**
 92:      * Get default Propel configuration path.
 93:      *
 94:      * @return string
 95:      */
 96:     public function getPropelConfigPath() {
 97:         return $this->propelConfigPath;
 98:     }
 99: 
100:     /**
101:      * Get configuration path relative to the root directory.
102:      *
103:      * @return string
104:      */
105:     public function getConfigPath() {
106:         return $this->configPath;
107:     }
108: 
109:     /**
110:      * Application constructor.
111:      *
112:      * @param string $appNamespace Application and/or vendor namespace, e.g. 'namespace', 'vendor\\namespace'
113:      * @param array  $values       Custom configuration
114:      * @param array  $fixPaths     Fix paths used to bootstrap files and directories
115:      *
116:      * @return Application
117:      *
118:      * @throws \InvalidArgumentException     Missing propel configuration file
119:      * @throws \InvalidArgumentException     Can not resolve project namespace
120:      * @throws InvalidConfigurationException Twig engine is not enabled, but extension configured in config.yml file
121:      * @throws \RuntimeException             File parameters.yml does not exist
122:      * @throws \Exception                    Throws raw exceptions in test environment
123:      */
124:     public function __construct($appNamespace = '', array $values = [], array $fixPaths = []) {
125:         // big error catch for nice debug
126:         try {
127:             $defaultValues = [
128:                 'environment' => 'prod',
129:                 'minion.usePropel' => true,
130:                 'minion.useTwig' => true,
131:             ];
132: 
133:             // default configuration
134:             parent::__construct(\array_replace_recursive($defaultValues, $values));
135: 
136:             // error handler
137:             $this->error(function(\Exception $ex, $code) {
138:                 return $this->minionError($ex, $code);
139:             });
140: 
141:             // calculate directories and paths
142:             $this->resolvePaths($fixPaths);
143: 
144:             // determine project namespace, if not defined
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:             // register services
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:                 // load Twig Extensions
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:             // load configuration
202:             $loader = new YamlFileLoader(new FileLocator($this->getRootDir() . $this->getConfigPath()));
203:             /** @var RouteCollection $routes */
204:             $routes = $loader->load('routing.yml');
205:             // fix controller namespaces
206:             if(\count($routes) > 0)
207:                 foreach($routes as $route)
208:                     /** @var Route $route */
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:             /** @var array $parameters */
219:             $parameters = Yaml::parse(\file_get_contents($parametersFile)) ?: [];
220:             $this['parameters'] = new ParameterBag($parameters);
221: 
222:             // load user-defined configuration and services
223:             $userConfigFile = $this->getRootDir() . $this->getConfigPath() . 'config.yml';
224:             if(\file_exists($userConfigFile)) {
225:                 /** @var array $userConfig */
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:                      * @var string $id
232:                      * @var array  $def
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:                             /** @var SilexServiceProviderInterface|ServiceProviderInterface $provider */
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:             // register events
265:             // inject self to the controller automatically
266:             $this['dispatcher']->addListener(KernelEvents::CONTROLLER, function(FilterControllerEvent $event) {
267:                 /** @var Controller $controller */
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:      * @internal
281:      *
282:      * Handle errors and exceptions thrown by entire application.
283:      *
284:      * @param \Exception $ex   Any exception
285:      * @param integer    $code HTTP status code
286:      *
287:      * @return Response Response to the client with nice error page
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:      * @internal
326:      *
327:      * Terminate application immediately and show exception to the client. Does not trigger any events.
328:      *
329:      * @param \Exception $exception Exception instance
330:      * @param integer    $code      HTTP status code
331:      *
332:      * @return void
333:      */
334:     public function fastAbort(\Exception $exception, $code = 500) {
335:         $response = $this->minionError($exception, $code);
336:         $response->send();
337:         die;
338:     }
339: 
340:     /**
341:      * Resolve paths to navigate through project.
342:      *
343:      * @param array $fixPaths User-defined path fixes
344:      *
345:      * @return void
346:      *
347:      * @throws \InvalidArgumentException Some paths are invalid
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: }
API documentation generated by ApiGen