mirror of
https://github.com/marcostoll/FF.git
synced 2025-01-16 22:28:15 +01:00
[FEATURE] [WIP] bootstrapping
This commit is contained in:
parent
68e7f0a4ec
commit
d973d2f34b
@ -16,10 +16,10 @@
|
||||
"php": ">=7.2",
|
||||
"fastforward/data-structures": "^1.0.0",
|
||||
"fastforward/factories": "^1.2.0",
|
||||
"symfony/config": "~4.3",
|
||||
"symfony/http-foundation": "~4.3",
|
||||
"symfony/routing": "~4.3",
|
||||
"symfony/yaml": "~4.3",
|
||||
"symfony/config": "~5.2",
|
||||
"symfony/http-foundation": "~5.2",
|
||||
"symfony/routing": "~5.2",
|
||||
"symfony/yaml": "~5.2",
|
||||
"twig/twig": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
143
src/Runtime/Bootstrap.php
Normal file
143
src/Runtime/Bootstrap.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of Bootstrap
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Runtime;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use FF\Factories\ServicesFactory;
|
||||
use FF\Factories\SF;
|
||||
use FF\Runtime\Exceptions\BootstrapException;
|
||||
use FF\Services\Exceptions\ResourceInvalidException;
|
||||
use FF\Services\Runtime\ConfigParser;
|
||||
|
||||
/**
|
||||
* Class Bootstrap
|
||||
*
|
||||
* @package FF\Runtime
|
||||
*/
|
||||
class Bootstrap
|
||||
{
|
||||
/**
|
||||
* Environment labels
|
||||
*/
|
||||
const ENV_PROD = 'production';
|
||||
const ENV_DEV = 'development';
|
||||
const ENV_TEST = 'testing';
|
||||
|
||||
/**
|
||||
* Adds a namespace location to Composer's class loader
|
||||
*
|
||||
* @param ClassLoader $loader
|
||||
* @param string $namespace
|
||||
* @param string $src
|
||||
* @param bool $prepend
|
||||
* @return $this
|
||||
*/
|
||||
public function addNamespace(ClassLoader $loader, string $namespace, string $src, bool $prepend = false)
|
||||
{
|
||||
$loader->addPsr4(rtrim($namespace, '\\') . '\\', rtrim($src, '/') . '/', $prepend);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores initial data in the system-wide registry
|
||||
*
|
||||
* Sets keys 'basePath' and 'environment' using the given argument values.
|
||||
*
|
||||
* @param string $basePath
|
||||
* @param string $environment
|
||||
* @param array $additionalData
|
||||
* @return $this
|
||||
*/
|
||||
public function initRegistry(string $basePath, string $environment = self::ENV_DEV, array $additionalData = [])
|
||||
{
|
||||
$data = array_merge([
|
||||
'basePath' => $basePath,
|
||||
'environment' => $environment
|
||||
], $additionalData);
|
||||
Registry::getInstance()->setData($data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the services factory
|
||||
*
|
||||
* If $environment is omitted, tries to retrieve it from the Registry.
|
||||
* If then an non-empty $environment is present, looks for an environment-specific services configuration
|
||||
* and merges its values into the given $servicesYml.
|
||||
*
|
||||
* @param string $servicesYml
|
||||
* @param string $environment
|
||||
* @return $this
|
||||
* @throws BootstrapException
|
||||
*/
|
||||
public function initServiceFactory(string $servicesYml, string $environment = null)
|
||||
{
|
||||
if (!is_file($servicesYml) || !is_readable($servicesYml)) {
|
||||
throw new BootstrapException('services yml [' . $servicesYml . '] not found or not readable');
|
||||
}
|
||||
|
||||
try {
|
||||
$configParser = new ConfigParser();
|
||||
|
||||
$contents = $configParser->load($servicesYml);
|
||||
$servicesConfig = $configParser->parse($contents);
|
||||
|
||||
do {
|
||||
if (empty($environment)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$environment = Registry::getInstance()->getField('environment');
|
||||
$envYml = $this->buildEnvironmentServicesYmlFileName($servicesYml, $environment);
|
||||
$envContents = $configParser->load($envYml);
|
||||
if (empty($envContents)) {
|
||||
// do nothing if no env-specific config file is present
|
||||
break;
|
||||
}
|
||||
$envConfig = $configParser->parse($envContents);
|
||||
|
||||
$servicesConfig = $configParser->merge($servicesConfig, $envConfig);
|
||||
} while (false);
|
||||
} catch (ResourceInvalidException $exception) {
|
||||
throw new BootstrapException('error while parsing services configuration', 0, $exception);
|
||||
}
|
||||
|
||||
SF::setInstance(new ServicesFactory($servicesConfig));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the file name with the suffix injected
|
||||
*
|
||||
* @param string $servicesYml
|
||||
* @param string $environment
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
protected function buildEnvironmentServicesYmlFileName(
|
||||
string $servicesYml,
|
||||
string $environment,
|
||||
string $delimiter = '-'
|
||||
) {
|
||||
$pathInfo = pathinfo($servicesYml);
|
||||
|
||||
if (!isset($pathInfo['extension'])) {
|
||||
return $servicesYml . $delimiter . $environment;
|
||||
}
|
||||
|
||||
return $pathInfo['dirname'] . DIRECTORY_SEPARATOR
|
||||
. $pathInfo['filename']
|
||||
. $delimiter . $environment
|
||||
. '.' . $pathInfo['extension'];
|
||||
}
|
||||
}
|
21
src/Runtime/Exceptions/BootstrapException.php
Normal file
21
src/Runtime/Exceptions/BootstrapException.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Class BootstrapException
|
||||
*
|
||||
* @package FF\Services\Exceptions
|
||||
* @author Marco Stoll <m.stoll@core4.de>
|
||||
* @link http://core4.de CORE4 GmbH & Co. KG
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace FF\Runtime\Exceptions;
|
||||
|
||||
/**
|
||||
* Class BootstrapException
|
||||
*
|
||||
* @package FF\Runtime\Exceptions
|
||||
*/
|
||||
class BootstrapException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
40
src/Runtime/Registry.php
Normal file
40
src/Runtime/Registry.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of Registry
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Runtime;
|
||||
|
||||
use FF\DataStructures\Record;
|
||||
|
||||
/**
|
||||
* Class Registry
|
||||
*
|
||||
* @package FF\Runtime
|
||||
*/
|
||||
class Registry extends Record
|
||||
{
|
||||
/**
|
||||
* @var Registry
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of this class
|
||||
*
|
||||
* return Registry
|
||||
*/
|
||||
public static function getInstance(): Registry
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new Registry();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ namespace FF\Services\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ConfigurationException
|
||||
*
|
||||
* @package FF\Services\Exceptions
|
||||
*/
|
||||
class ConfigurationException extends \RuntimeException
|
||||
{
|
||||
|
21
src/Services/Exceptions/ResourceInvalidException.php
Normal file
21
src/Services/Exceptions/ResourceInvalidException.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ResourceInvalidException
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ResourceInvalidException
|
||||
*
|
||||
* @package FF\Services\Exceptions
|
||||
*/
|
||||
class ResourceInvalidException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
110
src/Services/Runtime/ConfigParser.php
Normal file
110
src/Services/Runtime/ConfigParser.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ConfigParser
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services\Runtime;
|
||||
|
||||
use FF\Services\AbstractService;
|
||||
use FF\Services\Exceptions\ResourceInvalidException;
|
||||
use FF\Utils\ArrayUtils;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Class ConfigParser
|
||||
*
|
||||
* @package FF\Services\Runtime
|
||||
*/
|
||||
class ConfigParser extends AbstractService
|
||||
{
|
||||
/**
|
||||
* Loads the contents of a config file
|
||||
*
|
||||
* Replaces any occurrence of '<<$key>>', where $key is a key within $replacements.
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $replacements
|
||||
* @return string|null
|
||||
*/
|
||||
public function load(string $file, array $replacements = []): ?string
|
||||
{
|
||||
if (!is_file($file) || !is_readable($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($file);
|
||||
foreach ($replacements as $token => $value) {
|
||||
$contents = str_replace('<<' . $token . '>>', $value, $contents);
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the contents of a config file
|
||||
*
|
||||
* @param string $ymlContents
|
||||
* @return array
|
||||
* @throws ResourceInvalidException not valid yml
|
||||
*/
|
||||
public function parse(string $ymlContents): array
|
||||
{
|
||||
try {
|
||||
return Yaml::parse($ymlContents);
|
||||
} catch (ParseException $exception) {
|
||||
throw new ResourceInvalidException('not valid yml', 0, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively merges two config arrays
|
||||
*
|
||||
* @param array $config1
|
||||
* @param array $config2
|
||||
* @return array
|
||||
*/
|
||||
public function merge(array $config1, array $config2)
|
||||
{
|
||||
foreach (array_keys($config2) as $key) {
|
||||
// test if $key is new to $config1
|
||||
if (!array_key_exists($key, $config1)) {
|
||||
$config1[$key] = $config2[$key]; // copy key and value to $config1
|
||||
continue;
|
||||
}
|
||||
|
||||
// test if either value of $config1 or $config2 is non-array
|
||||
if (!is_array($config1[$key]) || !is_array($config2[$key])) {
|
||||
$config1[$key] = $config2[$key]; // replace value in $config1
|
||||
continue;
|
||||
}
|
||||
|
||||
// both values are arrays
|
||||
$isAssoc1 = ArrayUtils::isAssoc($config1[$key]);
|
||||
$isAssoc2 = ArrayUtils::isAssoc($config2[$key]);
|
||||
switch (true) {
|
||||
case $isAssoc1 != $isAssoc2 :
|
||||
// array types differ -> replace value in first array
|
||||
$config1[$key] = $config2[$key];
|
||||
break;
|
||||
case !$isAssoc1 && !$isAssoc2 :
|
||||
// both numeric arrays -> append second to first
|
||||
$config1[$key] = array_merge($config1[$key], $config2[$key]);
|
||||
break;
|
||||
case $isAssoc1 && $isAssoc2 :
|
||||
// both associative arrays -> start recursion
|
||||
$config1[$key] = $this->merge($config1[$key], $config2[$key]);
|
||||
break;
|
||||
default :
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $config1;
|
||||
}
|
||||
}
|
32
tests/Runtime/RegistryTest.php
Normal file
32
tests/Runtime/RegistryTest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of RegistryTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Runtime;
|
||||
|
||||
use FF\Runtime\Registry;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test RegistryTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class RegistryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetInstance()
|
||||
{
|
||||
$instance = Registry::getInstance();
|
||||
$this->assertInstanceOf(Registry::class, $instance);
|
||||
$this->assertSame($instance, Registry::getInstance());
|
||||
}
|
||||
}
|
33
tests/Services/Runtime/ConfigParserTest.php
Normal file
33
tests/Services/Runtime/ConfigParserTest.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ConfigParserTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
use FF\Services\Runtime\ConfigParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test ConfigParserTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class ConfigParserTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ConfigParser
|
||||
*/
|
||||
protected $uut;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->uut = new ConfigParser();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user