summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/app.php9
-rw-r--r--includes/engelsystem_provider.php13
-rw-r--r--includes/helper/internationalization_helper.php2
-rw-r--r--src/Application.php80
-rw-r--r--src/Container/ServiceProvider.php31
-rw-r--r--src/helpers.php50
-rw-r--r--tests/Unit/ApplicationTest.php147
7 files changed, 310 insertions, 22 deletions
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 00000000..fe0a97c1
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,9 @@
+<?php
+
+// Application config
+
+return [
+ // Service providers
+ 'providers' => [
+ ],
+];
diff --git a/includes/engelsystem_provider.php b/includes/engelsystem_provider.php
index e1669c57..3067ab62 100644
--- a/includes/engelsystem_provider.php
+++ b/includes/engelsystem_provider.php
@@ -27,6 +27,13 @@ $app = new Application(realpath(__DIR__ . DIRECTORY_SEPARATOR . '..'));
/**
+ * Bootstrap application
+ */
+$appConfig = $app->make(Config::class);
+$appConfig->set(app('path.config') . '/app.php');
+$app->bootstrap($appConfig);
+
+/**
* Load configuration
*/
$config = new Config();
@@ -40,6 +47,10 @@ if (file_exists(__DIR__ . '/../config/config.php')) {
));
}
+
+/**
+ * Configure application
+ */
date_default_timezone_set($config->get('timezone'));
@@ -55,7 +66,7 @@ $app->instance('request', $request);
/**
* Check for maintenance
*/
-if ($config->get('maintenance')) {
+if ($app->get('config')->get('maintenance')) {
echo file_get_contents(__DIR__ . '/../templates/maintenance.html');
die();
}
diff --git a/includes/helper/internationalization_helper.php b/includes/helper/internationalization_helper.php
index efbe5db5..7fa6518b 100644
--- a/includes/helper/internationalization_helper.php
+++ b/includes/helper/internationalization_helper.php
@@ -36,7 +36,7 @@ function gettext_init()
}
gettext_locale();
- bindtextdomain('default', realpath(__DIR__ . '/../../locale'));
+ bindtextdomain('default', app('path.lang'));
bind_textdomain_codeset('default', 'UTF-8');
textdomain('default');
}
diff --git a/src/Application.php b/src/Application.php
index 80538396..b62b28a9 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -2,7 +2,9 @@
namespace Engelsystem;
+use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
+use Engelsystem\Container\ServiceProvider;
use Psr\Container\ContainerInterface;
class Application extends Container
@@ -10,6 +12,16 @@ class Application extends Container
/** @var string|null */
protected $appPath = null;
+ /** @var bool */
+ protected $isBootstrapped = false;
+
+ /**
+ * Registered service providers
+ *
+ * @var array
+ */
+ protected $serviceProviders = [];
+
/**
* Application constructor.
*
@@ -36,15 +48,73 @@ class Application extends Container
}
/**
+ * @param string|ServiceProvider $provider
+ * @return ServiceProvider
+ */
+ public function register($provider)
+ {
+ if (is_string($provider)) {
+ $provider = $this->get($provider);
+ }
+
+ $this->serviceProviders[] = $provider;
+
+ $provider->register();
+
+ if ($this->isBootstrapped) {
+ $this->call([$provider, 'boot']);
+ }
+
+ return $provider;
+ }
+
+ /**
+ * Boot service providers
+ *
+ * @param Config|null $config
+ */
+ public function bootstrap(Config $config = null)
+ {
+ if ($this->isBootstrapped) {
+ return;
+ }
+
+ if ($config instanceof Config) {
+ foreach ($config->get('providers', []) as $provider) {
+ $this->register($provider);
+ }
+ }
+
+ foreach ($this->serviceProviders as $provider) {
+ $this->call([$provider, 'boot']);
+ }
+
+ $this->isBootstrapped = true;
+ }
+
+ protected function registerPaths()
+ {
+ $appPath = $this->appPath;
+
+ $this->instance('path', $appPath);
+ $this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config');
+ $this->instance('path.lang', $appPath . DIRECTORY_SEPARATOR . 'locale');
+ }
+
+ /**
+ * Set app base path
+ *
* @param string $appPath
* @return static
*/
public function setAppPath($appPath)
{
+ $appPath = realpath($appPath);
$appPath = rtrim($appPath, DIRECTORY_SEPARATOR);
$this->appPath = $appPath;
- $this->instance('path', $appPath);
+
+ $this->registerPaths();
return $this;
}
@@ -56,4 +126,12 @@ class Application extends Container
{
return $this->appPath;
}
+
+ /**
+ * @return bool
+ */
+ public function isBooted()
+ {
+ return $this->isBootstrapped;
+ }
}
diff --git a/src/Container/ServiceProvider.php b/src/Container/ServiceProvider.php
new file mode 100644
index 00000000..2a1bbebf
--- /dev/null
+++ b/src/Container/ServiceProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Engelsystem\Container;
+
+use Engelsystem\Application;
+
+abstract class ServiceProvider
+{
+ /** @var Application */
+ protected $app;
+
+ /**
+ * ServiceProvider constructor.
+ *
+ * @param Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Register container bindings
+ */
+ public function register() { }
+
+ /**
+ * Called after other services had been registered
+ */
+ public function boot() { }
+}
diff --git a/src/helpers.php b/src/helpers.php
index de303963..c3c727ec 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -24,6 +24,15 @@ function app($id = null)
}
/**
+ * @param string $path
+ * @return string
+ */
+function base_path($path = '')
+{
+ return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
+}
+
+/**
* Get or set config values
*
* @param string|array $key
@@ -47,6 +56,15 @@ function config($key = null, $default = null)
}
/**
+ * @param string $path
+ * @return string
+ */
+function config_path($path = '')
+{
+ return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
+}
+
+/**
* @param string $key
* @param mixed $default
* @return Request|mixed
@@ -79,22 +97,6 @@ function session($key = null, $default = null)
}
/**
- * @param string $template
- * @param mixed[] $data
- * @return Renderer|string
- */
-function view($template = null, $data = null)
-{
- $renderer = app('renderer');
-
- if (is_null($template)) {
- return $renderer;
- }
-
- return $renderer->render($template, $data);
-}
-
-/**
* @param string $path
* @param array $parameters
* @return UrlGenerator|string
@@ -109,3 +111,19 @@ function url($path = null, $parameters = [])
return $urlGenerator->to($path, $parameters);
}
+
+/**
+ * @param string $template
+ * @param mixed[] $data
+ * @return Renderer|string
+ */
+function view($template = null, $data = null)
+{
+ $renderer = app('renderer');
+
+ if (is_null($template)) {
+ return $renderer;
+ }
+
+ return $renderer->render($template, $data);
+}
diff --git a/tests/Unit/ApplicationTest.php b/tests/Unit/ApplicationTest.php
index 53fe3109..78310134 100644
--- a/tests/Unit/ApplicationTest.php
+++ b/tests/Unit/ApplicationTest.php
@@ -3,19 +3,23 @@
namespace Engelsystem\Test\Config;
use Engelsystem\Application;
+use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
+use Engelsystem\Container\ServiceProvider;
use PHPUnit\Framework\TestCase;
+use PHPUnit_Framework_MockObject_MockObject;
use Psr\Container\ContainerInterface;
+use ReflectionClass;
class ApplicationTest extends TestCase
{
/**
- * @covers \Engelsystem\Application::__construct
- * @covers \Engelsystem\Application::registerBaseBindings
+ * @covers \Engelsystem\Application::__construct
+ * @covers \Engelsystem\Application::registerBaseBindings
*/
public function testConstructor()
{
- $app = new Application();
+ $app = new Application('.');
$this->assertInstanceOf(Container::class, $app);
$this->assertInstanceOf(ContainerInterface::class, $app);
@@ -27,4 +31,141 @@ class ApplicationTest extends TestCase
$this->assertSame($app, Application::getInstance());
$this->assertSame($app, Container::getInstance());
}
+
+ /**
+ * @covers \Engelsystem\Application::setAppPath
+ * @covers \Engelsystem\Application::registerPaths
+ * @covers \Engelsystem\Application::path
+ */
+ public function testAppPath()
+ {
+ $app = new Application();
+
+ $this->assertFalse($app->has('path'));
+
+ $app->setAppPath('.');
+ $this->assertTrue($app->has('path'));
+ $this->assertTrue($app->has('path.config'));
+ $this->assertTrue($app->has('path.lang'));
+
+ $this->assertEquals(realpath('.'), $app->path());
+ $this->assertEquals(realpath('.') . '/config', $app->get('path.config'));
+
+ $app->setAppPath('./../');
+ $this->assertEquals(realpath('../') . '/config', $app->get('path.config'));
+ }
+
+ /**
+ * @covers \Engelsystem\Application::register
+ */
+ public function testRegister()
+ {
+ $app = new Application();
+
+ $serviceProvider = $this->mockServiceProvider($app, ['register']);
+ $serviceProvider->expects($this->once())
+ ->method('register');
+
+ $app->register($serviceProvider);
+
+ $anotherServiceProvider = $this->mockServiceProvider($app, ['register', 'boot']);
+ $anotherServiceProvider->expects($this->once())
+ ->method('register');
+ $anotherServiceProvider->expects($this->once())
+ ->method('boot');
+
+ $app->bootstrap();
+ $app->register($anotherServiceProvider);
+ }
+
+ /**
+ * @covers \Engelsystem\Application::register
+ */
+ public function testRegisterBoot()
+ {
+ $app = new Application();
+ $app->bootstrap();
+
+ $serviceProvider = $this->mockServiceProvider($app, ['register', 'boot']);
+ $serviceProvider->expects($this->once())
+ ->method('register');
+ $serviceProvider->expects($this->once())
+ ->method('boot');
+
+ $app->register($serviceProvider);
+ }
+
+ /**
+ * @covers \Engelsystem\Application::register
+ */
+ public function testRegisterClassName()
+ {
+ $app = new Application();
+
+ $mockClassName = $this->getMockClass(ServiceProvider::class);
+ $serviceProvider = $this->getMockBuilder($mockClassName)
+ ->setConstructorArgs([$app])
+ ->setMethods(['register'])
+ ->getMock();
+
+ $serviceProvider->expects($this->once())
+ ->method('register');
+
+ $app->instance($mockClassName, $serviceProvider);
+ $app->register($mockClassName);
+ }
+
+ /**
+ * @covers \Engelsystem\Application::bootstrap
+ * @covers \Engelsystem\Application::isBooted
+ */
+ public function testBootstrap()
+ {
+ /** @var PHPUnit_Framework_MockObject_MockObject|Application $app */
+ $app = $this->getMockBuilder(Application::class)
+ ->setMethods(['register'])
+ ->getMock();
+
+ $serviceProvider = $this->mockServiceProvider($app, ['boot']);
+ $serviceProvider->expects($this->once())
+ ->method('boot');
+
+ $app->expects($this->once())
+ ->method('register')
+ ->with($serviceProvider);
+
+ $config = $this->getMockBuilder(Config::class)
+ ->getMock();
+
+ $config->expects($this->once())
+ ->method('get')
+ ->with('providers')
+ ->willReturn([$serviceProvider]);
+
+ $property = (new ReflectionClass($app))->getProperty('serviceProviders');
+ $property->setAccessible(true);
+ $property->setValue($app, [$serviceProvider]);
+
+ $app->bootstrap($config);
+
+ $this->assertTrue($app->isBooted());
+
+ // Run bootstrap another time to ensure that providers are registered only once
+ $app->bootstrap($config);
+ }
+
+ /**
+ * @param Application $app
+ * @param array $methods
+ * @return PHPUnit_Framework_MockObject_MockObject|ServiceProvider
+ */
+ protected function mockServiceProvider(Application $app, $methods = [])
+ {
+ $serviceProvider = $this->getMockBuilder(ServiceProvider::class)
+ ->setConstructorArgs([$app])
+ ->setMethods($methods)
+ ->getMockForAbstractClass();
+
+ return $serviceProvider;
+ }
}