Implemented service provider functionality

This commit is contained in:
Igor Scheller 2017-09-22 14:02:02 +02:00
parent 783c58611a
commit d49e49c364
7 changed files with 310 additions and 22 deletions

9
config/app.php Normal file
View File

@ -0,0 +1,9 @@
<?php
// Application config
return [
// Service providers
'providers' => [
],
];

View File

@ -26,6 +26,13 @@ require_once __DIR__ . '/autoload.php';
$app = new Application(realpath(__DIR__ . DIRECTORY_SEPARATOR . '..')); $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 * Load configuration
*/ */
@ -40,6 +47,10 @@ if (file_exists(__DIR__ . '/../config/config.php')) {
)); ));
} }
/**
* Configure application
*/
date_default_timezone_set($config->get('timezone')); date_default_timezone_set($config->get('timezone'));
@ -55,7 +66,7 @@ $app->instance('request', $request);
/** /**
* Check for maintenance * Check for maintenance
*/ */
if ($config->get('maintenance')) { if ($app->get('config')->get('maintenance')) {
echo file_get_contents(__DIR__ . '/../templates/maintenance.html'); echo file_get_contents(__DIR__ . '/../templates/maintenance.html');
die(); die();
} }

View File

@ -36,7 +36,7 @@ function gettext_init()
} }
gettext_locale(); gettext_locale();
bindtextdomain('default', realpath(__DIR__ . '/../../locale')); bindtextdomain('default', app('path.lang'));
bind_textdomain_codeset('default', 'UTF-8'); bind_textdomain_codeset('default', 'UTF-8');
textdomain('default'); textdomain('default');
} }

View File

@ -2,7 +2,9 @@
namespace Engelsystem; namespace Engelsystem;
use Engelsystem\Config\Config;
use Engelsystem\Container\Container; use Engelsystem\Container\Container;
use Engelsystem\Container\ServiceProvider;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
class Application extends Container class Application extends Container
@ -10,6 +12,16 @@ class Application extends Container
/** @var string|null */ /** @var string|null */
protected $appPath = null; protected $appPath = null;
/** @var bool */
protected $isBootstrapped = false;
/**
* Registered service providers
*
* @var array
*/
protected $serviceProviders = [];
/** /**
* Application constructor. * 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 * @param string $appPath
* @return static * @return static
*/ */
public function setAppPath($appPath) public function setAppPath($appPath)
{ {
$appPath = realpath($appPath);
$appPath = rtrim($appPath, DIRECTORY_SEPARATOR); $appPath = rtrim($appPath, DIRECTORY_SEPARATOR);
$this->appPath = $appPath; $this->appPath = $appPath;
$this->instance('path', $appPath);
$this->registerPaths();
return $this; return $this;
} }
@ -56,4 +126,12 @@ class Application extends Container
{ {
return $this->appPath; return $this->appPath;
} }
/**
* @return bool
*/
public function isBooted()
{
return $this->isBootstrapped;
}
} }

View File

@ -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() { }
}

View File

@ -23,6 +23,15 @@ function app($id = null)
return Application::getInstance()->get($id); return Application::getInstance()->get($id);
} }
/**
* @param string $path
* @return string
*/
function base_path($path = '')
{
return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
}
/** /**
* Get or set config values * Get or set config values
* *
@ -46,6 +55,15 @@ function config($key = null, $default = null)
return $config->get($key, $default); return $config->get($key, $default);
} }
/**
* @param string $path
* @return string
*/
function config_path($path = '')
{
return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
}
/** /**
* @param string $key * @param string $key
* @param mixed $default * @param mixed $default
@ -78,22 +96,6 @@ function session($key = null, $default = null)
return $session->get($key, $default); return $session->get($key, $default);
} }
/**
* @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 string $path
* @param array $parameters * @param array $parameters
@ -109,3 +111,19 @@ function url($path = null, $parameters = [])
return $urlGenerator->to($path, $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);
}

View File

@ -3,19 +3,23 @@
namespace Engelsystem\Test\Config; namespace Engelsystem\Test\Config;
use Engelsystem\Application; use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Container\Container; use Engelsystem\Container\Container;
use Engelsystem\Container\ServiceProvider;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use ReflectionClass;
class ApplicationTest extends TestCase class ApplicationTest extends TestCase
{ {
/** /**
* @covers \Engelsystem\Application::__construct * @covers \Engelsystem\Application::__construct
* @covers \Engelsystem\Application::registerBaseBindings * @covers \Engelsystem\Application::registerBaseBindings
*/ */
public function testConstructor() public function testConstructor()
{ {
$app = new Application(); $app = new Application('.');
$this->assertInstanceOf(Container::class, $app); $this->assertInstanceOf(Container::class, $app);
$this->assertInstanceOf(ContainerInterface::class, $app); $this->assertInstanceOf(ContainerInterface::class, $app);
@ -27,4 +31,141 @@ class ApplicationTest extends TestCase
$this->assertSame($app, Application::getInstance()); $this->assertSame($app, Application::getInstance());
$this->assertSame($app, Container::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;
}
} }