diff --git a/external/import.php b/external/import.php index 674f614e5..8ddad5bab 100644 --- a/external/import.php +++ b/external/import.php @@ -1,7 +1,6 @@ getSaver(); while (!feof($fp)) { $line = fgets($fp); diff --git a/phpunit.xml b/phpunit.xml index 924278134..527c2585e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,5 @@ - + diff --git a/src/Application.php b/src/Application.php new file mode 100644 index 000000000..b24229aa9 --- /dev/null +++ b/src/Application.php @@ -0,0 +1,48 @@ +register(new ServiceProvider\ServiceProvider()); + $this->register(new ServiceProvider\ConfigProvider()); + $this->register(new ServiceProvider\PdoStorageProvider()); + $this->register(new ServiceProvider\MongoStorageProvider()); + $this->register(new ServiceProvider\SlimProvider()); + } + + public function run(): void + { + $this->boot()->getSlim()->run(); + } + + public function boot(): self + { + if (!$this->booted) { + $this->register(new ServiceProvider\RouteProvider()); + $this->booted = true; + } + + return $this; + } + + public function getSlim(): App + { + return $this['app']; + } + + public function getSaver(): SaverInterface + { + return $this['saver']; + } +} diff --git a/src/ServiceContainer.php b/src/ServiceContainer.php deleted file mode 100644 index 498fb935e..000000000 --- a/src/ServiceContainer.php +++ /dev/null @@ -1,249 +0,0 @@ -boot(); - } - - return static::$_instance; - } - - public function __construct() - { - parent::__construct(); - $this->setupPaths($this); - $this->register(new ConfigProvider()); - $this->slimApp(); - $this->services(); - $this->storageDriverPdo($this); - $this->storageDriverMongoDb($this); - $this->controllers(); - } - - public function boot(): void - { - $this->register(new RouteProvider()); - } - - private function setupPaths(self $app): void - { - $app['app.dir'] = dirname(__DIR__); - $app['app.template_dir'] = dirname(__DIR__) . '/templates'; - $app['app.config_dir'] = dirname(__DIR__) . '/config'; - $app['app.cache_dir'] = static function ($c) { - return $c['config']['cache'] ?? dirname(__DIR__) . '/cache'; - }; - } - - // Create the Slim app. - private function slimApp(): void - { - $this['view'] = static function ($c) { - // Configure Twig view for slim - $view = new Twig(); - - $view->twigTemplateDirs = [ - $c['app.template_dir'], - ]; - $view->parserOptions = [ - 'charset' => 'utf-8', - 'cache' => $c['app.cache_dir'], - 'auto_reload' => true, - 'strict_variables' => false, - 'autoescape' => 'html', - ]; - - // set global variables to templates - $view->appendData([ - 'date_format' => $c['config']['date.format'], - ]); - - return $view; - }; - - $this['app'] = static function ($c) { - if ($c['config']['timezone']) { - date_default_timezone_set($c['config']['timezone']); - } - - $app = new App($c['config']); - - $view = $c['view']; - $view->parserExtensions = [ - new TwigExtension($app), - ]; - $app->view($view); - - return $app; - }; - } - - /** - * Add common service objects to the container. - */ - private function services(): void - { - $this['searcher'] = static function ($c) { - $saver = $c['config']['save.handler']; - - return $c["searcher.$saver"]; - }; - - $this['saver'] = static function ($c) { - $saver = $c['config']['save.handler']; - - return new NormalizingSaver($c["saver.$saver"]); - }; - } - - private function storageDriverPdo(Container $app): void - { - $app['pdo'] = static function ($app) { - if (!class_exists(PDO::class)) { - throw new RuntimeException('Required extension ext-pdo is missing'); - } - - $driver = explode(':', $app['config']['pdo']['dsn'], 2)[0]; - - // check the PDO driver is available - if (!in_array($driver, PDO::getAvailableDrivers(), true)) { - $drivers = implode(',', PDO::getAvailableDrivers()) ?: '(none)'; - throw new RuntimeException("Required PDO driver $driver is missing, Available drivers: $drivers"); - } - - $options = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ]; - - if ($driver === 'mysql') { - $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SQL_MODE=ANSI_QUOTES;'; - } - - return new PDO( - $app['config']['pdo']['dsn'], - $app['config']['pdo']['user'], - $app['config']['pdo']['pass'], - $options - ); - }; - - $app[PdoRepository::class] = static function ($app) { - return new PdoRepository($app['pdo'], $app['config']['pdo']['table']); - }; - - $app['searcher.pdo'] = static function ($app) { - return new PdoSearcher($app[PdoRepository::class]); - }; - - $app['saver.pdo'] = static function ($app) { - return new Saver\PdoSaver($app[PdoRepository::class]); - }; - } - - private function storageDriverMongoDb(Container $app): void - { - // NOTE: db.host, db.options, db.driverOptions, db.db are @deprecated and will be removed in the future - $app['mongodb.database'] = static function ($app) { - $config = $app['config']; - $mongodb = $config['mongodb'] ?? []; - - return $config['db.db'] ?? $mongodb['database'] ?? 'xhgui'; - }; - - $app[MongoDB::class] = static function ($app) { - $database = $app['mongodb.database']; - /** @var MongoClient $client */ - $client = $app[MongoClient::class]; - $mongoDB = $client->selectDb($database); - $mongoDB->results->findOne(); - - return $mongoDB; - }; - - $app[MongoClient::class] = static function ($app) { - if (!class_exists(Manager::class)) { - throw new RuntimeException('Required extension ext-mongodb missing'); - } - - $config = $app['config']; - $mongodb = $config['mongodb'] ?? []; - $options = $config['db.options'] ?? $mongodb['options'] ?? []; - $driverOptions = $config['db.driverOptions'] ?? $mongodb['driverOptions'] ?? []; - $server = $config['db.host'] ?? sprintf('mongodb://%s:%s', $mongodb['hostname'], $mongodb['port']); - - return new MongoClient($server, $options, $driverOptions); - }; - - $app['searcher.mongodb'] = static function ($app) { - return new MongoSearcher($app[MongoDB::class]); - }; - - $app['saver.mongodb'] = static function ($app) { - /** @var MongoDB $mongoDB */ - $mongoDB = $app[MongoDB::class]; - /** @var MongoCollection $collection */ - $collection = $mongoDB->results; - - return new Saver\MongoSaver($collection); - }; - } - - /** - * Add controllers to the DI container. - */ - private function controllers(): void - { - $this['watchController'] = $this->factory(static function ($c) { - return new Controller\WatchController($c['app'], $c['searcher']); - }); - - $this['runController'] = $this->factory(static function ($c) { - return new Controller\RunController($c['app'], $c['searcher']); - }); - - $this['customController'] = $this->factory(static function ($c) { - return new Controller\CustomController($c['app'], $c['searcher']); - }); - - $this['waterfallController'] = $this->factory(static function ($c) { - return new Controller\WaterfallController($c['app'], $c['searcher']); - }); - - $this['importController'] = $this->factory(static function ($c) { - return new Controller\ImportController($c['app'], $c['saver'], $c['config']['upload.token']); - }); - - $this['metricsController'] = $this->factory(static function ($c) { - return new Controller\MetricsController($c['app'], $c['searcher']); - }); - } -} diff --git a/src/ServiceProvider/MongoStorageProvider.php b/src/ServiceProvider/MongoStorageProvider.php new file mode 100644 index 000000000..a8cdb0983 --- /dev/null +++ b/src/ServiceProvider/MongoStorageProvider.php @@ -0,0 +1,64 @@ +selectDb($database); + $mongoDB->results->findOne(); + + return $mongoDB; + }; + + $app[MongoClient::class] = static function ($app) { + if (!class_exists(Manager::class)) { + throw new RuntimeException('Required extension ext-mongodb missing'); + } + + $config = $app['config']; + $mongodb = $config['mongodb'] ?? []; + $options = $config['db.options'] ?? $mongodb['options'] ?? []; + $driverOptions = $config['db.driverOptions'] ?? $mongodb['driverOptions'] ?? []; + $server = $config['db.host'] ?? sprintf('mongodb://%s:%s', $mongodb['hostname'], $mongodb['port']); + + return new MongoClient($server, $options, $driverOptions); + }; + + $app['searcher.mongodb'] = static function ($app) { + return new MongoSearcher($app[MongoDB::class]); + }; + + $app['saver.mongodb'] = static function ($app) { + /** @var MongoDB $mongoDB */ + $mongoDB = $app[MongoDB::class]; + /** @var MongoCollection $collection */ + $collection = $mongoDB->results; + + return new MongoSaver($collection); + }; + } +} diff --git a/src/ServiceProvider/PdoStorageProvider.php b/src/ServiceProvider/PdoStorageProvider.php new file mode 100644 index 000000000..5bf22f181 --- /dev/null +++ b/src/ServiceProvider/PdoStorageProvider.php @@ -0,0 +1,58 @@ + PDO::ERRMODE_EXCEPTION, + ]; + + if ($driver === 'mysql') { + $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SQL_MODE=ANSI_QUOTES;'; + } + + return new PDO( + $app['config']['pdo']['dsn'], + $app['config']['pdo']['user'], + $app['config']['pdo']['pass'], + $options + ); + }; + + $app[PdoRepository::class] = static function ($app) { + return new PdoRepository($app['pdo'], $app['config']['pdo']['table']); + }; + + $app['searcher.pdo'] = static function ($app) { + return new PdoSearcher($app[PdoRepository::class]); + }; + + $app['saver.pdo'] = static function ($app) { + return new PdoSaver($app[PdoRepository::class]); + }; + } +} diff --git a/src/ServiceProvider/RouteProvider.php b/src/ServiceProvider/RouteProvider.php index dd0bece69..bb0a9d0c4 100644 --- a/src/ServiceProvider/RouteProvider.php +++ b/src/ServiceProvider/RouteProvider.php @@ -15,6 +15,7 @@ class RouteProvider implements ServiceProviderInterface public function register(Container $di): void { $this->registerRoutes($di, $di['app']); + $this->registerControllers($di); } private function registerRoutes(Container $di, App $app): void @@ -37,7 +38,7 @@ private function registerRoutes(Container $di, App $app): void // Profile Runs routes $app->get('/', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $response = $app->response(); @@ -46,7 +47,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/view', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $response = $app->response(); @@ -55,7 +56,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/delete', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->deleteForm($request); @@ -63,7 +64,7 @@ private function registerRoutes(Container $di, App $app): void $app->post('/run/delete', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->deleteSubmit($request); @@ -71,19 +72,19 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/delete_all', static function () use ($di): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $controller->deleteAllForm(); })->setName('run.deleteAll.form'); $app->post('/run/delete_all', static function () use ($di): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $controller->deleteAllSubmit(); })->setName('run.deleteAll.submit'); $app->get('/url/view', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->url($request); @@ -91,7 +92,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/compare', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->compare($request); @@ -99,7 +100,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/symbol', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->symbol($request); @@ -107,7 +108,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/symbol/short', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->symbolShort($request); @@ -115,7 +116,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/callgraph', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $controller->callgraph($request); @@ -123,7 +124,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/callgraph/data', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $response = $app->response(); @@ -132,7 +133,7 @@ private function registerRoutes(Container $di, App $app): void $app->get('/run/callgraph/dot', static function () use ($di, $app): void { /** @var Controller\RunController $controller */ - $controller = $di['runController']; + $controller = $di[Controller\RunController::class]; $request = $app->request(); $response = $app->response(); @@ -142,7 +143,7 @@ private function registerRoutes(Container $di, App $app): void // Import route $app->post('/run/import', static function () use ($di, $app): void { /** @var Controller\ImportController $controller */ - $controller = $di['importController']; + $controller = $di[Controller\ImportController::class]; $request = $app->request(); $response = $app->response(); @@ -152,13 +153,13 @@ private function registerRoutes(Container $di, App $app): void // Watch function routes. $app->get('/watch', static function () use ($di): void { /** @var Controller\WatchController $controller */ - $controller = $di['watchController']; + $controller = $di[Controller\WatchController::class]; $controller->get(); })->setName('watch.list'); $app->post('/watch', static function () use ($di, $app): void { /** @var Controller\WatchController $controller */ - $controller = $di['watchController']; + $controller = $di[Controller\WatchController::class]; $request = $app->request(); $controller->post($request); @@ -167,13 +168,13 @@ private function registerRoutes(Container $di, App $app): void // Custom report routes. $app->get('/custom', static function () use ($di): void { /** @var Controller\CustomController $controller */ - $controller = $di['customController']; + $controller = $di[Controller\CustomController::class]; $controller->get(); })->setName('custom.view'); $app->get('/custom/help', static function () use ($di, $app): void { /** @var Controller\CustomController $controller */ - $controller = $di['customController']; + $controller = $di[Controller\CustomController::class]; $request = $app->request(); $controller->help($request); @@ -181,7 +182,7 @@ private function registerRoutes(Container $di, App $app): void $app->post('/custom/query', static function () use ($di, $app): void { /** @var Controller\CustomController $controller */ - $controller = $di['customController']; + $controller = $di[Controller\CustomController::class]; $request = $app->request(); $response = $app->response(); @@ -191,13 +192,13 @@ private function registerRoutes(Container $di, App $app): void // Waterfall routes $app->get('/waterfall', static function () use ($di): void { /** @var Controller\WaterfallController $controller */ - $controller = $di['waterfallController']; + $controller = $di[Controller\WaterfallController::class]; $controller->index(); })->setName('waterfall.list'); $app->get('/waterfall/data', static function () use ($di, $app): void { /** @var Controller\WaterfallController $controller */ - $controller = $di['waterfallController']; + $controller = $di[Controller\WaterfallController::class]; $request = $app->request(); $response = $app->response(); @@ -207,10 +208,37 @@ private function registerRoutes(Container $di, App $app): void // Metrics $app->get('/metrics', static function () use ($di, $app): void { /** @var Controller\MetricsController $controller */ - $controller = $di['metricsController']; + $controller = $di[Controller\MetricsController::class]; $response = $app->response(); $controller->metrics($response); })->setName('metrics'); } + + private function registerControllers(Container $app): void + { + $app[Controller\WatchController::class] = $app->factory(static function ($app) { + return new Controller\WatchController($app['app'], $app['searcher']); + }); + + $app[Controller\RunController::class] = $app->factory(static function ($app) { + return new Controller\RunController($app['app'], $app['searcher']); + }); + + $app[Controller\CustomController::class] = $app->factory(static function ($app) { + return new Controller\CustomController($app['app'], $app['searcher']); + }); + + $app[Controller\WaterfallController::class] = $app->factory(static function ($app) { + return new Controller\WaterfallController($app['app'], $app['searcher']); + }); + + $app[Controller\ImportController::class] = $app->factory(static function ($app) { + return new Controller\ImportController($app['app'], $app['saver'], $app['config']['upload.token']); + }); + + $app[Controller\MetricsController::class] = $app->factory(static function ($app) { + return new Controller\MetricsController($app['app'], $app['searcher']); + }); + } } diff --git a/src/ServiceProvider/ServiceProvider.php b/src/ServiceProvider/ServiceProvider.php new file mode 100644 index 000000000..429decc92 --- /dev/null +++ b/src/ServiceProvider/ServiceProvider.php @@ -0,0 +1,44 @@ +setupPaths($app); + $this->setupServices($app); + } + + private function setupPaths(Container $app): void + { + $app['app.dir'] = dirname(__DIR__, 2); + $app['app.template_dir'] = $app['app.dir'] . '/templates'; + $app['app.config_dir'] = $app['app.dir'] . '/config'; + $app['app.cache_dir'] = static function ($app) { + return $app['config']['cache'] ?? $app['app.dir'] . '/cache'; + }; + } + + /** + * Add common service objects to the container. + */ + private function setupServices(Container $app): void + { + $app['searcher'] = static function ($app) { + $saver = $app['config']['save.handler']; + + return $app["searcher.$saver"]; + }; + + $app['saver'] = static function ($app) { + $saver = $app['config']['save.handler']; + + return new NormalizingSaver($app["saver.$saver"]); + }; + } +} diff --git a/src/ServiceProvider/SlimProvider.php b/src/ServiceProvider/SlimProvider.php new file mode 100644 index 000000000..9033f6456 --- /dev/null +++ b/src/ServiceProvider/SlimProvider.php @@ -0,0 +1,57 @@ +twigTemplateDirs = [ + $c['app.template_dir'], + ]; + $view->parserOptions = [ + 'charset' => 'utf-8', + 'cache' => $c['app.cache_dir'], + 'auto_reload' => true, + 'strict_variables' => false, + 'autoescape' => 'html', + ]; + + // set global variables to templates + $view->appendData([ + 'date_format' => $c['config']['date.format'], + ]); + + return $view; + }; + + $c['app'] = static function ($c) { + if ($c['config']['timezone']) { + date_default_timezone_set($c['config']['timezone']); + } + + $app = new App($c['config']); + + $view = $c['view']; + $view->parserExtensions = [ + new TwigExtension($app), + ]; + $app->view($view); + + return $app; + }; + } +} diff --git a/tests/Controller/WatchTest.php b/tests/Controller/WatchTest.php index 109b4e00d..d5de4c052 100644 --- a/tests/Controller/WatchTest.php +++ b/tests/Controller/WatchTest.php @@ -9,8 +9,8 @@ class WatchTest extends TestCase { public function setUp(): void { - $this->skipIfPdo('Watchers not implemented'); parent::setUp(); + $this->skipIfPdo('Watchers not implemented'); Environment::mock([ 'SCRIPT_NAME' => 'index.php', diff --git a/tests/LazyContainerProperties.php b/tests/LazyContainerProperties.php index 17c5c804b..29a1f4157 100644 --- a/tests/LazyContainerProperties.php +++ b/tests/LazyContainerProperties.php @@ -6,38 +6,38 @@ use MongoDB; use Slim\Slim as App; use Slim\View; -use XHGui\Controller\ImportController; -use XHGui\Controller\RunController; -use XHGui\Controller\WatchController; +use XHGui\Application; +use XHGui\Controller; use XHGui\Saver\SaverInterface; use XHGui\Searcher\MongoSearcher; use XHGui\Searcher\SearcherInterface; -use XHGui\ServiceContainer; use XHGui\Twig\TwigExtension; trait LazyContainerProperties { use LazyPropertiesTrait; - /** @var ServiceContainer */ + /** @var Application */ protected $di; - /** @var ImportController */ + /** @var Controller\ImportController */ protected $import; /** @var MongoSearcher */ protected $mongo; /** @var MongoDB */ protected $mongodb; - /** @var RunController */ + /** @var Controller\RunController */ protected $runs; /** @var App */ protected $app; + /** @var array */ + protected $config; /** @var SearcherInterface */ protected $searcher; /** @var SaverInterface */ protected $saver; /** @var View */ protected $view; - /** @var WatchController */ + /** @var Controller\WatchController */ protected $watches; protected function setupProperties(): void @@ -45,6 +45,7 @@ protected function setupProperties(): void $this->initLazyProperties([ 'di', 'app', + 'config', 'import', 'mongo', 'mongodb', @@ -58,9 +59,13 @@ protected function setupProperties(): void protected function getDi() { - $di = new ServiceContainer(); + $di = new Application(); $config = $di['config']; + // Use a test databases + // TODO: do the same for PDO. currently PDO uses DSN syntax and has too many variations + $di['mongodb.database'] = 'test_xhgui'; + /** @var App $app */ $app = $this->getMockBuilder(App::class) ->setMethods(['redirect', 'render', 'urlFor']) @@ -84,9 +89,14 @@ protected function getApp() return $this->di['app']; } + protected function getConfig() + { + return $this->di['config']; + } + protected function getImport() { - return $this->di['importController']; + return $this->di[Controller\ImportController::class]; } protected function getMongo() @@ -106,7 +116,7 @@ protected function getSearcher() protected function getRuns() { - return $this->di['runController']; + return $this->di[Controller\RunController::class]; } protected function getSaver() @@ -121,6 +131,6 @@ protected function getView(): View protected function getWatches() { - return $this->di['watchController']; + return $this->di[Controller\WatchController::class]; } } diff --git a/tests/Searcher/MongoTest.php b/tests/Searcher/MongoTest.php index 3edbfcc3c..a6d3ccf1b 100644 --- a/tests/Searcher/MongoTest.php +++ b/tests/Searcher/MongoTest.php @@ -11,8 +11,8 @@ class MongoTest extends TestCase { public function setUp(): void { - $this->skipIfPdo('This is MongoDB test'); parent::setUp(); + $this->skipIfPdo('This is MongoDB test'); $this->mongodb->watches->drop(); $this->importFixture($this->di['saver.mongodb']); } diff --git a/tests/TestCase.php b/tests/TestCase.php index ab2a83156..fe16532ea 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,7 +3,6 @@ namespace XHGui\Test; use XHGui\Saver\SaverInterface; -use XHGui\ServiceContainer; abstract class TestCase extends \PHPUnit\Framework\TestCase { @@ -37,7 +36,7 @@ protected function loadFixture(string $fileName): array protected function skipIfPdo($details = null): void { - $saveHandler = ServiceContainer::instance()['config']['save.handler']; + $saveHandler = $this->config['save.handler']; if ($saveHandler !== 'pdo') { return; diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 9d7dc2399..000000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,17 +0,0 @@ -run();