Skip to content

Commit

Permalink
Implement config object
Browse files Browse the repository at this point in the history
  • Loading branch information
flangofas committed Apr 30, 2020
1 parent 6cb08f4 commit 999baf3
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 3 deletions.
8 changes: 7 additions & 1 deletion bin/slim
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ if (file_exists(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
}

$app = new Application();
//Config SHOULD be placed by init command in the project's root directory
$configDir = dirname(dirname(__DIR__));

$app = new Application($configDir);

//$app->add(new \Slim\Console\Command\InitCommand());

$app->run();
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"require": {
"php": "^7.2",
"symfony/console": "^5.0",
"symfony/config": "^5.0"
"symfony/config": "^5.0",
"ext-json": "*"
},
"require-dev": {
"adriansuter/php-autoload-override": "^1.0",
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
parameters:
level: max
inferPrivatePropertyTypeFromConstructor: true
checkGenericClassInNonGenericObjectType: false
19 changes: 18 additions & 1 deletion src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace Slim\Console;

use Slim\Console\Config\Config;
use Slim\Console\Config\ConfigResolver;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -19,9 +21,14 @@ class Application extends SymfonyApplication
{
private const VERSION = '0.1';

public function __construct()
/** @var Config|null */
protected $config;

public function __construct(string $configDir)
{
parent::__construct('Slim Console', self::VERSION);

$this->setConfig($configDir);
}

/**
Expand All @@ -48,4 +55,14 @@ public function doRun(InputInterface $input, OutputInterface $output): int

return parent::doRun($input, $output);
}

public function getConfig(): ?Config
{
return $this->config;
}

public function setConfig(string $configDir): void
{
$this->config = (new ConfigResolver($configDir))->loadConfig();
}
}
42 changes: 42 additions & 0 deletions src/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Console\Command;

use Slim\Console\Application;
use Slim\Console\Config\Config;
use Symfony\Component\Console\Command\Command as SymfonyCommand;

abstract class AbstractCommand extends SymfonyCommand
{
/**
* @return Config|null
*/
public function getConfig(): ?Config
{
$app = $this->getApplication();
if ($app instanceof Application === false) {
return null;
}

return $app->getConfig();
}

/**
* @param string $configDir
*/
public function setConfig(string $configDir): void
{
$app = $this->getApplication();
if ($app instanceof Application) {
$app->setConfig($configDir);
}
}
}
173 changes: 173 additions & 0 deletions src/Config/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim-Console/blob/0.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Console\Config;

use ArrayAccess;

use function array_key_exists;
use function array_merge;
use function ctype_lower;
use function is_null;
use function preg_replace;
use function strtoupper;

class Config implements ArrayAccess
{
private const ENV_PREFIX = 'SLIM_CONSOLE_';

/**
* @var array<mixed>
*/
private $default = [
'bootstrapDir' => DIRECTORY_SEPARATOR . 'app',
'commandsDir' => DIRECTORY_SEPARATOR . 'Application/Commands',
'indexDir' => DIRECTORY_SEPARATOR . 'public',
'indexFile' => DIRECTORY_SEPARATOR . 'index.php',
'sourceDir' => DIRECTORY_SEPARATOR . 'src',
];

/**
* @var array<mixed>
*/
private $params = [];

/**
* Config constructor.
* @param array<mixed> $params
* @param string $configDir
*/
public function __construct(array $params = [], string $configDir = '')
{
if ($configDir !== '') {
$this->set('rootDir', $configDir);
}
$this->setAll($params);
}

/**
* @return array<mixed>
*/
public function all(): array
{
return $this->params;
}

/**
* @param array<mixed> $params
* @return void
*/
public function setAll(array $params): void
{
$params = array_merge($this->default, $params);

foreach ($params as $key => $value) {
//Env variables take precedence
$this->params[$key] = $this->getEnvironmentVariableValue((string) $key) ?: $this->get('rootDir') . $value;
}
}

/**
* @param string $key
* @return mixed|null
*/
public function get(string $key)
{
return $this->params[$key] ?? null;
}

/**
* @param string $key
* @param mixed $value
*/
public function set(string $key, $value): void
{
$this->params[$key] = $value;
}

/**
* @param string $key
* @return bool
*/
public function has(string $key): bool
{
return isset($this->params[$key]);
}

/**
* @param string $key
*/
public function delete(string $key): void
{
unset($this->params[$key]);
}

/**
* @param mixed $key
* @return bool
*/
public function offsetExists($key): bool
{
return $this->has($key);
}

/**
* @param mixed $key
* @return mixed|null
*/
public function offsetGet($key)
{
return $this->get($key);
}

/**
* @param mixed $key
* @param mixed $value
*/
public function offsetSet($key, $value): void
{
$this->set($key, $value);
}

/**
* @param mixed $key
*/
public function offsetUnset($key): void
{
$this->delete($key);
}

/**
* @param string $key
* @return string|null
*/
private function getEnvironmentVariableValue(string $key): ?string
{
//Return nothing for unknown keys
if (array_key_exists($key, $this->default) === false) {
return null;
}

if (ctype_lower($key) === true) {
return strtoupper($key);
}

//Convert CamelCase to Snake_Case
$key = preg_replace('/(.)(?=[A-Z])/u', '$1' . '_', $key);
if (is_null($key)) {
return null;
}

$key = self::ENV_PREFIX . strtoupper($key);
$value = getenv($key);

return $value ?: null;
}
}
101 changes: 101 additions & 0 deletions src/Config/ConfigResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Slim\Console\Config;

use InvalidArgumentException;
use ReflectionClass;
use Slim\Console\Exception\ConfigNotFoundException;

use function array_filter;
use function file_exists;
use function file_get_contents;
use function implode;
use function is_array;
use function is_null;
use function json_decode;
use function strpos;

class ConfigResolver
{
public const FORMAT_JSON = 'json';
public const FORMAT_PHP = 'php';
//public const FORMAT_YAML = 'yaml';
private const CONFIG_FILENAME = 'slim-console.config';

/** @var string|null */
private $file = null;

/** @var string */
private $dir;

public function __construct(string $dir)
{
$this->dir = $dir;
}

public function loadConfig(): ?Config
{
$this->locateConfig();

return $this->loadConfigByFormat();
}

public function getFile(): string
{
return $this->file ?? "";
}

protected function locateConfig(): void
{
$reflection = new ReflectionClass($this);
$filterFormats = function ($value, $key) {
return strpos($key, 'FORMAT') === 0;
};
$formats = array_filter($reflection->getConstants(), $filterFormats, ARRAY_FILTER_USE_BOTH);

foreach ($formats as $possibleFormat) {
$fileName = $this->dir . DIRECTORY_SEPARATOR . self::CONFIG_FILENAME . '.' . $possibleFormat;
if (file_exists($fileName)) {
$this->file = $fileName;

break;
}
}

if (is_null($this->file)) {
throw new ConfigNotFoundException(
'Please create config file, supported formats: ' . implode(',', $formats)
);
}
}

protected function loadConfigByFormat(): ?Config
{
if (is_null($this->file)) {
return null;
}

$format = pathinfo($this->file, PATHINFO_EXTENSION);

if ($format === self::FORMAT_PHP) {
$array = require($this->file);
if (is_array($array) === false) {
throw new InvalidArgumentException("The file $this->file should return an array");
}

return new Config($array, $this->dir);
}

if ($format === self::FORMAT_JSON) {
$string = (string) file_get_contents($this->file);
$array = json_decode($string, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException("The file $this->file should be a valid JSON");
}

return new Config($array, $this->dir);
}

return null;
}
}
Loading

0 comments on commit 999baf3

Please sign in to comment.