Skip to content

Commit 1c4872c

Browse files
authored
Merge pull request #4734 from paulbalandan/env-spark-command
Add environment spark command
2 parents 59e34a1 + 2e96bde commit 1c4872c

File tree

3 files changed

+292
-36
lines changed

3 files changed

+292
-36
lines changed

contributing/internals.rst

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
CodeIgniter Internals Overview
33
##############################
44

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

99
Dependencies
1010
============
1111

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

1919
public function __construct(Foo $foo=null)
@@ -27,17 +27,17 @@ Type hinting
2727
============
2828

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

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

3535
Abstractions
3636
============
3737

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

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

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

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

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

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

6565
Namespaces and Files
6666
====================
6767

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

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

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

8282
Interfaces
8383
----------
8484

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

9191
The Router package mentioned above includes the
@@ -95,8 +95,8 @@ interfaces to provide the abstractions for the two classes in the package.
9595
Handlers
9696
--------
9797

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

@@ -105,34 +105,44 @@ See the Log and Session packages for examples.
105105
Configuration
106106
=============
107107

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

112112
Autoloader
113113
==========
114114

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

119119
Command-Line Support
120120
====================
121121

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

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

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

133133
Documentation
134134
=============
135135

136-
All packages must contain appropriate documentation that matches the tone and
137-
style of the rest of the user guide. In most cases, the top portion of the package's
136+
All packages must contain appropriate documentation that matches the tone and
137+
style of the rest of the user guide. In most cases, the top portion of the package's
138138
page should be treated in tutorial fashion, while the second half would be a class reference.
139+
140+
Modification of the ``env`` file
141+
================================
142+
143+
CodeIgniter is shipped with a template ``env`` file to support adding secrets too sensitive to
144+
be stored in a version control system. Contributors adding new entries to the env file should
145+
always ensure that these entries are commented, i.e., starting with a hash (``#``). This is
146+
because we have spark commands that actually copy the template file to a ``.env`` file (which
147+
is actually the live version actually read by CodeIgniter for secrets) if the latter is missing.
148+
As much as possible, we do not want settings to go live unexpectedly without the user's knowledge.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
namespace CodeIgniter\Commands\Utilities;
4+
5+
use CodeIgniter\CLI\BaseCommand;
6+
use CodeIgniter\CLI\CLI;
7+
use CodeIgniter\Config\DotEnv;
8+
9+
/**
10+
* Command to display the current environment,
11+
* or set a new one in the `.env` file.
12+
*/
13+
final class Environment extends BaseCommand
14+
{
15+
/**
16+
* The group the command is lumped under
17+
* when listing commands.
18+
*
19+
* @var string
20+
*/
21+
protected $group = 'CodeIgniter';
22+
23+
/**
24+
* The Command's name
25+
*
26+
* @var string
27+
*/
28+
protected $name = 'env';
29+
30+
/**
31+
* The Command's short description
32+
*
33+
* @var string
34+
*/
35+
protected $description = 'Retrieves the current environment, or set a new one.';
36+
37+
/**
38+
* The Command's usage
39+
*
40+
* @var string
41+
*/
42+
protected $usage = 'env [<environment>]';
43+
44+
/**
45+
* The Command's arguments
46+
*
47+
* @var array<string, string>
48+
*/
49+
protected $arguments = [
50+
'environment' => '[Optional] The new environment to set. If none is provided, this will print the current environment.',
51+
];
52+
53+
/**
54+
* The Command's options
55+
*
56+
* @var array
57+
*/
58+
protected $options = [];
59+
60+
/**
61+
* Allowed values for environment. `testing` is excluded
62+
* since spark won't work on it.
63+
*
64+
* @var array<int, string>
65+
*/
66+
private static $knownTypes = [
67+
'production',
68+
'development',
69+
];
70+
71+
/**
72+
* @inheritDoc
73+
*
74+
* @param array<string, mixed> $params
75+
*
76+
* @return void
77+
*/
78+
public function run(array $params)
79+
{
80+
if ($params === [])
81+
{
82+
CLI::write(sprintf('Your environment is currently set as %s.', CLI::color($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, 'green')));
83+
CLI::newLine();
84+
85+
return;
86+
}
87+
88+
$env = strtolower(array_shift($params));
89+
90+
if ($env === 'testing')
91+
{
92+
CLI::error('The "testing" environment is reserved for PHPUnit testing.', 'light_gray', 'red');
93+
CLI::error('You will not be able to run spark under a "testing" environment.', 'light_gray', 'red');
94+
CLI::newLine();
95+
96+
return;
97+
}
98+
99+
if (! in_array($env, self::$knownTypes, true))
100+
{
101+
CLI::error(sprintf('Invalid environment type "%s". Expected one of "%s".', $env, implode('" and "', self::$knownTypes)), 'light_gray', 'red');
102+
CLI::newLine();
103+
104+
return;
105+
}
106+
107+
if (! $this->writeNewEnvironmentToEnvFile($env))
108+
{
109+
CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red');
110+
CLI::newLine();
111+
112+
return;
113+
}
114+
115+
// force DotEnv to reload the new environment
116+
// however we cannot redefine the ENVIRONMENT constant
117+
putenv('CI_ENVIRONMENT');
118+
unset($_ENV['CI_ENVIRONMENT'], $_SERVER['CI_ENVIRONMENT']);
119+
(new DotEnv(ROOTPATH))->load();
120+
121+
CLI::write(sprintf('Environment is successfully changed to "%s".', $env), 'green');
122+
CLI::write('The ENVIRONMENT constant will be changed in the next script execution.');
123+
CLI::newLine();
124+
}
125+
126+
/**
127+
* @see https://regex101.com/r/4sSORp/1 for the regex in action
128+
*
129+
* @param string $newEnv
130+
*
131+
* @return boolean
132+
*/
133+
private function writeNewEnvironmentToEnvFile(string $newEnv): bool
134+
{
135+
$baseEnv = ROOTPATH . 'env';
136+
$envFile = ROOTPATH . '.env';
137+
138+
if (! is_file($envFile))
139+
{
140+
if (! is_file($baseEnv))
141+
{
142+
CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow');
143+
CLI::write('It is impossible to write the new environment type.', 'yellow');
144+
CLI::newLine();
145+
146+
return false;
147+
}
148+
149+
copy($baseEnv, $envFile);
150+
}
151+
152+
$pattern = preg_quote($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, '/');
153+
$pattern = sprintf('/^[#\s]*CI_ENVIRONMENT[=\s]+%s$/m', $pattern);
154+
155+
return file_put_contents(
156+
$envFile,
157+
preg_replace($pattern, "\nCI_ENVIRONMENT = {$newEnv}", file_get_contents($envFile), -1, $count)
158+
) !== false && $count > 0;
159+
}
160+
}

0 commit comments

Comments
 (0)