-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement config object #13
Conversation
7c14298
to
999baf3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a preliminary review, before writing all the tests let's nail down the initial mechanisms.
Thanks for this, great work!
public function setConfig(string $configDir): void | ||
{ | ||
$app = $this->getApplication(); | ||
if ($app instanceof Application) { | ||
$app->setConfig($configDir); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should throw here to indicate to the user that the parent Application
does not support this in case this command is being used out of context. Same for the getConfig()
method
* @param string $key | ||
* @return mixed|null | ||
*/ | ||
public function get(string $key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we should omit from using ArrayAccess
and use named getters/setters:
getBootstrapDir(): string
getCommandsDir(): ?string
getIndexDir(): string
getIndexFile(): string
getSourceDir(): string
Same for setters, here we return the Config
object so we can chain set
calls:
setBootstrapDir(string $dir): Config
...
I think that this object should be immutable but extensible. So maybe an interface is necessary. Another point I would like to make is that in no instance should getters return null, as this will create downstream errors. These should all be validated, required parameters. The only parameter that could be null is commandsDir
.
So instead of instantiating the configuration file with an array I would do something like:
public function __construct(
string $sourceDir,
string $bootstrapDir,
string $indexDir,
string $indexFile,
?string $commandsDir = null,
)
Then you could have a static instantiator:
public static function fromArray(array $params): Config
{
// ... do array validation to ensure params contain required keys ...
return new self(...);
}
public static function fromEnvironment(): Config
{
// ... do array validation to ensure params contain required keys ...
return new self(...);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's wrap $this->file
with {$this->file}
$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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's wrap $this->file
with {$this->file}
|
||
public function loadConfig(): ?Config | ||
{ | ||
$this->locateConfig(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we should do:
$this->file = $this->locateConfig();
In the locateConfig(): string
method we can instead return early from the proposed switch
statement.
if (is_null($this->file)) { | ||
throw new ConfigNotFoundException( | ||
'Please create config file, supported formats: ' . implode(',', $formats) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this if
statement can be removed since we would reach this from not returning early. The ConfigurationNotFoundException
still stands though.
{ | ||
$this->locateConfig(); | ||
|
||
return $this->loadConfigByFormat(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since ENV variables takes precedence over all other formats here what we should try first is $this->attemptCreatingConfigFromEnv(): Config
which would call Config::fromEnvironment()
(this method should throw something like CannotCreateConfigFromEnvironmentException
. If we catch this, then we proceed to try and locate PHP
config then JSON
then YAML
Here we should do:
try {
return $this->attemptCreatingConfigFromEnv();
catch (CannotCreateConfigFromEnvironmentException $e) {
return $this->attemptCreatingConfigFromSupportedFormats();
}
return $this->config; | ||
} | ||
|
||
public function setConfig(string $configDir): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it weird, that getConfig
returns a Config
object while setConfig
takes a string
? I would suggest to name the method resolveAndLoadConfig
which in turn calls setConfig(Config $config)
.
If so, the same holds for \Slim\Console\Command\AbstractCommand
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @adriansuter
*/ | ||
private $default = [ | ||
'bootstrapDir' => DIRECTORY_SEPARATOR . 'app', | ||
'commandsDir' => DIRECTORY_SEPARATOR . 'Application/Commands', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a DIRECTORY_SEPARATOR
between Application
and Commands
too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the default we do. For user passed input that’ll be up to them!
* @param string $key | ||
* @return mixed|null | ||
*/ | ||
public function get(string $key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
$this->dir = $dir; | ||
} | ||
|
||
public function loadConfig(): ?Config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can basically instantiate a ConfigResolver
and then load the config multiple times? Why should we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This resolver should only have one public method called resolve
which either returns a Config
object or throws.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adriansuter @l0gicgate
Should we disallow to load the config again when it is already loaded via ConfigResolver?
*/ | ||
private $default = [ | ||
'bootstrapDir' => DIRECTORY_SEPARATOR . 'app', | ||
'commandsDir' => DIRECTORY_SEPARATOR . 'Application/Commands', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use DIRECTORY_SEPARATOR
between Application
and Commands
too.
And I think src/Commands
will be a more semantic place for user commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at how a DDD project is normally structured I would say that Application/Commands
is better since you’ll typically only have only 3 root level folders being:
- Application
- Domain
- Infrastructure
Could even go one step further and do Application/Console/Commands
since in there you would technically want to separate your Console and Web layers.
@@ -48,4 +55,14 @@ public function doRun(InputInterface $input, OutputInterface $output): int | |||
|
|||
return parent::doRun($input, $output); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment block.
{ | ||
return $this->config; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment block.
@@ -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__)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case where we use Slim Console as a dependency in project, dirname(dirname(__DIR__))
will return the correct project root, but what if we use Console as a global dependency? It will return ~/.composer
. Can we make this part dynamic?
* @param array<mixed> $params | ||
* @return void | ||
*/ | ||
public function setAll(array $params): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make it fluent and return self.
); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment block.
private $file = null; | ||
|
||
/** @var string */ | ||
private $dir; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use a more descriptive name, for example configDirectory
.
private const CONFIG_FILENAME = 'slim-console.config'; | ||
|
||
/** @var string|null */ | ||
private $file = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use a more descriptive name, for example configFile
.
|
||
public function loadConfig(): ?Config | ||
{ | ||
$this->locateConfig(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we handle the exception?
$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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we provide more information to the user, for example, using json_last_error_msg()
?
@flangofas I created a new draft in #14. I think it's probably an easier starting point since you essentially would have to entirely rewrite this from the reviewers' directions. Please don't take offense. Thank you for this initial contribution it gave us a good base to see where we wanted to start at least so it wasn't all done for nothing. |
@l0gicgate No offense taken, I agree. The decisions were not made and it wasn't quite clear how it is going to be implemented, I should've asked more questions in the first place. |
This is the implementation of #6
It can be accessed by the Application or Command Object.
Use cases:
Additional params can be stored as follows:
Supported formats