Skip to content

Add environment spark command #4734

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

Merged
merged 3 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 46 additions & 36 deletions contributing/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
CodeIgniter Internals Overview
##############################

This guide should help contributors understand how the core of the framework works,
and what needs to be done when creating new functionality. Specifically, it
This guide should help contributors understand how the core of the framework works,
and what needs to be done when creating new functionality. Specifically, it
details the information needed to create new packages for the core.

Dependencies
============

All packages should be designed to be completely isolated from the rest of the
packages, if possible. This will allow them to be used in projects outside of CodeIgniter.
Basically, this means that any dependencies should be kept to a minimum.
All packages should be designed to be completely isolated from the rest of the
packages, if possible. This will allow them to be used in projects outside of CodeIgniter.
Basically, this means that any dependencies should be kept to a minimum.
Any dependencies must be able to be passed into the constructor. If you do need to use one
of the other core packages, you can create that in the constructor using the
of the other core packages, you can create that in the constructor using the
Services class, as long as you provide a way for dependencies to override that::

public function __construct(Foo $foo=null)
Expand All @@ -27,17 +27,17 @@ Type hinting
============

PHP7 provides the ability to `type hint <http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration>`_
method parameters and return types. Use it where possible. Return type hinting
method parameters and return types. Use it where possible. Return type hinting
is not always practical, but do try to make it work.

At this time, we are not using strict type hinting.

Abstractions
============

The amount of abstraction required to implement a solution should be the minimal
amount required. Every layer of abstraction brings additional levels of technical
debt and unnecessary complexity. That said, don't be afraid to use it when it's
The amount of abstraction required to implement a solution should be the minimal
amount required. Every layer of abstraction brings additional levels of technical
debt and unnecessary complexity. That said, don't be afraid to use it when it's
needed and can help things.

* Don't create a new container class when an array will do just fine.
Expand All @@ -46,46 +46,46 @@ needed and can help things.
Testing
=======

Any new packages submitted to the framework must be accompanied by unit tests.
Any new packages submitted to the framework must be accompanied by unit tests.
The target is 80%+ code coverage of all classes within the package.

* Test only public methods, not protected and private unless the method really needs it due to complexity.
* Don't just test that the method works, but test for all fail states, thrown exceptions, and other pathways through your code.

You should be aware of the extra assertions that we have made, provisions for
accessing private properties for testing, and mock services.
You should be aware of the extra assertions that we have made, provisions for
accessing private properties for testing, and mock services.
We have also made a **CITestStreamFilter** to capture test output.
Do check out similar tests in ``tests/system/``, and read the "Testing" section
Do check out similar tests in ``tests/system/``, and read the "Testing" section
in the user guide, before you dig in to your own.

Some testing needs to be done in a separate process, in order to setup the
PHP globals to mimic test situations properly. See
Some testing needs to be done in a separate process, in order to setup the
PHP globals to mimic test situations properly. See
``tests/system/HTTP/ResponseSendTest`` for an example of this.

Namespaces and Files
====================

All new packages should live under the ``CodeIgniter`` namespace.
All new packages should live under the ``CodeIgniter`` namespace.
The package itself will need its own sub-namespace
that collects all related files into one grouping, like ``CodeIgniter\HTTP``.

Files MUST be named the same as the class they hold, and they must match the
:doc:`Style Guide <./styleguide.rst>`, meaning CamelCase class and file names.
They should be in their own directory that matches the sub-namespace under the
Files MUST be named the same as the class they hold, and they must match the
:doc:`Style Guide <./styleguide.rst>`, meaning CamelCase class and file names.
They should be in their own directory that matches the sub-namespace under the
**system** directory.

Take the Router class as an example. The Router lives in the ``CodeIgniter\Router``
Take the Router class as an example. The Router lives in the ``CodeIgniter\Router``
namespace. Its two main classes,
**RouteCollection** and **Router**, are in the files **system/Router/RouteCollection.php** and
**system/Router/Router.php** respectively.

Interfaces
----------

Most base classes should have an interface defined for them.
Most base classes should have an interface defined for them.
At the very least this allows them to be easily mocked
and passed to other classes as a dependency, without breaking the type-hinting.
The interface names should match the name of the class with "Interface" appended
and passed to other classes as a dependency, without breaking the type-hinting.
The interface names should match the name of the class with "Interface" appended
to it, like ``RouteCollectionInterface``.

The Router package mentioned above includes the
Expand All @@ -95,8 +95,8 @@ interfaces to provide the abstractions for the two classes in the package.
Handlers
--------

When a package supports multiple "drivers", the convention is to place them in
a **Handlers** directory, and name the child classes as Handlers.
When a package supports multiple "drivers", the convention is to place them in
a **Handlers** directory, and name the child classes as Handlers.
You will often find that creating a ``BaseHandler``, that the child classes can
extend, to be beneficial in keeping the code DRY.

Expand All @@ -105,34 +105,44 @@ See the Log and Session packages for examples.
Configuration
=============

Should the package require user-configurable settings, you should create a new
file just for that package under **app/Config**.
Should the package require user-configurable settings, you should create a new
file just for that package under **app/Config**.
The file name should generally match the package name.

Autoloader
==========

All files within the package should be added to **system/Config/AutoloadConfig.php**,
in the "classmap" property. This is only used for core framework files, and helps
All files within the package should be added to **system/Config/AutoloadConfig.php**,
in the "classmap" property. This is only used for core framework files, and helps
to minimize file system scans and keep performance high.

Command-Line Support
====================

CodeIgniter has never been known for it's strong CLI support. However, if your
package could benefit from it, create a new file under **system/Commands**.
CodeIgniter has never been known for it's strong CLI support. However, if your
package could benefit from it, create a new file under **system/Commands**.
The class contained within is simply a controller that is intended for CLI
usage only. The ``index()`` method should provide a list of available commands
usage only. The ``index()`` method should provide a list of available commands
provided by that package.

Routes must be added to **system/Config/Routes.php** using the ``cli()`` method
Routes must be added to **system/Config/Routes.php** using the ``cli()`` method
to ensure it is not accessible through the browser, but is restricted to the CLI only.

See the **MigrationsCommand** file for an example.

Documentation
=============

All packages must contain appropriate documentation that matches the tone and
style of the rest of the user guide. In most cases, the top portion of the package's
All packages must contain appropriate documentation that matches the tone and
style of the rest of the user guide. In most cases, the top portion of the package's
page should be treated in tutorial fashion, while the second half would be a class reference.

Modification of the ``env`` file
================================

CodeIgniter is shipped with a template ``env`` file to support adding secrets too sensitive to
be stored in a version control system. Contributors adding new entries to the env file should
always ensure that these entries are commented, i.e., starting with a hash (``#``). This is
because we have spark commands that actually copy the template file to a ``.env`` file (which
is actually the live version actually read by CodeIgniter for secrets) if the latter is missing.
As much as possible, we do not want settings to go live unexpectedly without the user's knowledge.
160 changes: 160 additions & 0 deletions system/Commands/Utilities/Environment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php

namespace CodeIgniter\Commands\Utilities;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Config\DotEnv;

/**
* Command to display the current environment,
* or set a new one in the `.env` file.
*/
final class Environment extends BaseCommand
{
/**
* The group the command is lumped under
* when listing commands.
*
* @var string
*/
protected $group = 'CodeIgniter';

/**
* The Command's name
*
* @var string
*/
protected $name = 'env';

/**
* The Command's short description
*
* @var string
*/
protected $description = 'Retrieves the current environment, or set a new one.';

/**
* The Command's usage
*
* @var string
*/
protected $usage = 'env [<environment>]';

/**
* The Command's arguments
*
* @var array<string, string>
*/
protected $arguments = [
'environment' => '[Optional] The new environment to set. If none is provided, this will print the current environment.',
];

/**
* The Command's options
*
* @var array
*/
protected $options = [];

/**
* Allowed values for environment. `testing` is excluded
* since spark won't work on it.
*
* @var array<int, string>
*/
private static $knownTypes = [
'production',
'development',
];

/**
* @inheritDoc
*
* @param array<string, mixed> $params
*
* @return void
*/
public function run(array $params)
{
if ($params === [])
{
CLI::write(sprintf('Your environment is currently set as %s.', CLI::color($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, 'green')));
CLI::newLine();

return;
}

$env = strtolower(array_shift($params));

if ($env === 'testing')
{
CLI::error('The "testing" environment is reserved for PHPUnit testing.', 'light_gray', 'red');
CLI::error('You will not be able to run spark under a "testing" environment.', 'light_gray', 'red');
CLI::newLine();

return;
}

if (! in_array($env, self::$knownTypes, true))
{
CLI::error(sprintf('Invalid environment type "%s". Expected one of "%s".', $env, implode('" and "', self::$knownTypes)), 'light_gray', 'red');
CLI::newLine();

return;
}

if (! $this->writeNewEnvironmentToEnvFile($env))
{
CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red');
CLI::newLine();

return;
}

// force DotEnv to reload the new environment
// however we cannot redefine the ENVIRONMENT constant
putenv('CI_ENVIRONMENT');
unset($_ENV['CI_ENVIRONMENT'], $_SERVER['CI_ENVIRONMENT']);
(new DotEnv(ROOTPATH))->load();

CLI::write(sprintf('Environment is successfully changed to "%s".', $env), 'green');
CLI::write('The ENVIRONMENT constant will be changed in the next script execution.');
CLI::newLine();
}

/**
* @see https://regex101.com/r/4sSORp/1 for the regex in action
*
* @param string $newEnv
*
* @return boolean
*/
private function writeNewEnvironmentToEnvFile(string $newEnv): bool
{
$baseEnv = ROOTPATH . 'env';
$envFile = ROOTPATH . '.env';

if (! is_file($envFile))
{
if (! is_file($baseEnv))
{
CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow');
CLI::write('It is impossible to write the new environment type.', 'yellow');
CLI::newLine();

return false;
}

copy($baseEnv, $envFile);
}

$pattern = preg_quote($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, '/');
$pattern = sprintf('/^[#\s]*CI_ENVIRONMENT[=\s]+%s$/m', $pattern);

return file_put_contents(
$envFile,
preg_replace($pattern, "\nCI_ENVIRONMENT = {$newEnv}", file_get_contents($envFile), -1, $count)
) !== false && $count > 0;
}
}
Loading