Skip to content

Commit

Permalink
Adds support for different config sources (directus#1048)
Browse files Browse the repository at this point in the history
* Adds configuration schemas, tests and benchmark code

* Rename file context, add tests as an example on how to put it to work

* Core integration

Fixed settings being removed from config
Added support for stdout/stdout logger outputs
Added some config exceptions
Fixed a bug on group defaults
Added support for loading configs from ENV
Unified how app is created
Added schema tests

* Fixed unknown project config loading
  • Loading branch information
WoLfulus authored and binal-7span committed Jul 1, 2019
1 parent 92e77c5 commit 289f475
Show file tree
Hide file tree
Showing 26 changed files with 1,098 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ Icon
.DS_Store
*.log
.cache
.vscode

# Ignore dev files
/.idea
node_modules
yarn.lock
package-lock.json
/nbproject/
/tests/api/tmp/

# Don't track composer phar/vendors
composer.phar
Expand Down
37 changes: 37 additions & 0 deletions benchmarks/api/Config/SourceBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Directus\Config\Source;


class SourceBench
{
/**
* @Revs(10000)
* @Iterations(3)
*/
public function benchEnv()
{
$_ENV = [
"A" => 1
];
Context::from_env();
}

/**
* @Revs(10000)
* @Iterations(3)
*/
public function benchFile()
{
Context::from_file(__DIR__ . "/../../../tests/api/Config/sources/source.php");
}

/**
* @Revs(10000)
* @Iterations(3)
*/
public function benchJson()
{
Context::from_json(__DIR__ . "/../../../tests/api/Config/sources/source.json");
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"webonyx/graphql-php": "^0.13.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7.25"
"phpunit/phpunit": "^5.7.25",
"phpbench/phpbench": "@dev"
},
"suggest": {
"paragonie/random_compat": "Generates cryptographically more secure pseudo-random bytes",
Expand Down
20 changes: 20 additions & 0 deletions phpbench.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"bootstrap": "vendor/autoload.php",
"path": "benchmarks",
"time_unit": "milliseconds",
"outputs": {
"directus": {
"extends": "html",
"file": "benchmark.html",
"title": "Directus Benchmarks"
}
},
"reports": {
"directus": {
"extends": "aggregate"
}
},
"php_config": {
"variables_order": "EGPCS"
}
}
2 changes: 1 addition & 1 deletion src/core/Directus/Application/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public function setCheckRequirementsFunction(\Closure $function)
protected function createConfig(array $appConfig)
{
return [
'settings' => ArrayUtils::pull($appConfig, 'settings', []),
'settings' => $appConfig['settings'],
'config' => function () use ($appConfig) {
return new Config($appConfig);
}
Expand Down
6 changes: 4 additions & 2 deletions src/core/Directus/Application/CoreServicesProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,14 @@ protected function getLogger()

$filenameFormat = '%s.%s.log';
foreach (Logger::getLevels() as $name => $level) {
if ($path !== "php://stdout" && $path !== "php://stderr") {
$path . '/' . sprintf($filenameFormat, strtolower($name), date('Y-m-d'));
}
$handler = new StreamHandler(
$path . '/' . sprintf($filenameFormat, strtolower($name), date('Y-m-d')),
$path,
$level,
false
);

$handler->setFormatter($formatter);
$logger->pushHandler($handler);
}
Expand Down
108 changes: 108 additions & 0 deletions src/core/Directus/Config/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace Directus\Config;

/**
* Config context interface
*/
class Context
{
/**
* Transforms an array of strings into a complex object
* @example
* $obj = Context::expand(['a', 'b', 'c'], 12345);
* $obj == [
* 'a' => [
* 'b' => [
* 'c' => 12345
* ]
* ]
* ];
*/
private static function expand(&$target, $path, $value)
{
$segment = array_shift($path);
if (sizeof($path) === 0) { // leaf
if (!is_array($target)) {
// TODO: raise warning - overwriting value
$target = [];
}
if (array_key_exists($segment, $target)) {
// TODO: raise warning - overwriting group
}
$target[$segment] = $value;
return;
}
if (!isset($target[$segment])) {
$target[$segment] = [];
}
Context::expand($target[$segment], $path, $value);
}

/**
* Normalize the array indexes
*/
private static function normalize(&$target) {
if (!is_array($target)) {
return;
}

$sort = false;
foreach ($target as $key => $value) {
Context::normalize($target[$key]);
$sort |= is_numeric($key);
}

if ($sort) {
// TODO: which one?
sort($target, SORT_NUMERIC);
// vs.
//$target = array_values($target);
}
}

/**
* Source
*/
public static function from_map($source) {
$target = [];
ksort($source);
foreach ($source as $key => $value) {
Context::expand($target, explode('_', strtolower($key)), $value);
}
Context::normalize($target);
return $target;
}

/**
* Create
*/
public static function from_env()
{
if (empty($_ENV)) {
throw new \Error('No environment variables available. Check php_ini "variables_order" value.');
}
return Context::from_map($_ENV);
}

/**
* Loads variables from PHP file
*/
public static function from_file($file) {
return require $file;
}

/**
* Loads variables from PHP file
*/
public static function from_array($array) {
return $array;
}

/**
* Loads variables from JSON file
*/
public static function from_json($file) {
return json_decode(file_get_contents($file));
}
}
22 changes: 22 additions & 0 deletions src/core/Directus/Config/Exception/InvalidProjectException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Directus\Config\Exception;

use Directus\Exception\ErrorException;

class InvalidProjectException extends ErrorException
{
const ERROR_CODE = 22;

public function __construct($previous = null)
{
$message = 'Current configuration context only supports default project name (_)';

parent::__construct($message, static::ERROR_CODE, $previous);
}

public function getStatusCode()
{
return 400;
}
}
17 changes: 17 additions & 0 deletions src/core/Directus/Config/Exception/UnknownProjectException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Directus\Config\Exception;

use Directus\Exception\ErrorException;

class UnknownProjectException extends ErrorException
{
const ERROR_CODE = 22;

public function __construct($project, $previous = null)
{
$message = "Unknown project: ${project}";

parent::__construct($message, static::ERROR_CODE, $previous);
}
}
116 changes: 116 additions & 0 deletions src/core/Directus/Config/Schema/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Directus\Config\Schema;

/**
* Group node
*/
abstract class Base implements Node
{
/**
* Node key
* @var string
*/
private $_key = null;

/**
* Node name
* @var string
*/
private $_name = null;

/**
* Node children
* @var Node[]
*/
private $_children = null;

/**
* Node parent
* @var Node
*/
private $_parent = null;

/**
* Node is optional
* @var boolean
*/
private $_optional = null;

/**
* Constructor
*/
public function __construct($name, $children)
{
$this->_optional = substr($name, -1) == '?';
if ($this->_optional) {
$name = substr($name, 0, -1);
}
$this->_key = str_replace('_', '', strtolower($name));
$this->_name = strtolower($name);
$this->_children = $children;
$this->_parent = null;
foreach ($children as &$child) {
$child->parent($this);
}
}

/**
* Returns the node key
* @return string
*/
public function key()
{
return $this->_key;
}

/**
* Returns the node name
* @return string
*/
public function name()
{
return $this->_name;
}

/**
* Returns the parent node
* @return Node
*/
public function parent($value = false)
{
if ($value !== false) {
$this->_parent = $value;
if ($this->_parent !== null) {
if ($this->_parent->optional() === true) {
$this->_optional = true;
}
}
}
return $this->_parent;
}

/**
* Returns the children nodes
* @return Node[]
*/
public function children($value = false)
{
if ($value !== false) {
$this->_children = $value;
}
return $this->_children;
}

/**
* Returns wether the node is optional or not
* @return boolean
*/
public function optional($value = null)
{
if ($value !== null) {
$this->_optional = $value;
}
return $this->_optional;
}
}
10 changes: 10 additions & 0 deletions src/core/Directus/Config/Schema/Exception/OmitException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Directus\Config\Schema\Exception;

use Directus\Exception\ErrorException;

class OmitException extends ErrorException
{

}
Loading

0 comments on commit 289f475

Please sign in to comment.