This is the last chapter of the book, except the appendices (which have useful information too; don't skip them).
We will conclude the adventure we started 12 chapters ago with the features of Yii 2, which are not related to the business value of the application being constructed but are related to its infrastructure.
When working with the code base together with a team of other developers and routinely deploying code between test and production servers, you will inevitably face some problems you need to solve to continue effectively delivering new features.
First, we'll learn some nice tricks to manage the application configuration to accommodate different deploy targets.
Secondly, we'll talk about the database migrations we were using the entire time until now. Hopefully, we have enough practical knowledge about using migrations. Here, we'll talk about the reasons to use them and some nice tricks to maintain them more effectively.
Finally, we'll look at the Yii console applications, the side we implicitly used several times in this book but never explicitly talked about.
We all know the following problem:
Imagine we are developing a web application, and we do it at our local workstation. The application uses the locally-installed MySQL instance with some particular database name, username, and a password. When we deploy this application, it'll use the MySQL instance installed at the deploy target, over which we can or cannot have control in choosing names and passwords. Even if we have total control over the deploy target and can use the same connection settings, it's highly impractical to enforce our own usernames and passwords on other people in the team, who are working on the same web application on their own workstations.
As the Yii application configuration is just a PHP script returning an associative array, we have a simple way of solving this problem: all pieces of configuration that should be specified for each deploy target individually can be moved to separate files. The main configuration will just include their contents using standard require()
calls.
In fact, we already have such a setup in our example CRM application. The following are the lines of code that include other pieces of configuration into our main configuration file, which is ultimately passed to the constructor of yiiwebApplication
:
return [ // … global settings skipped here ... 'components' => [ 'db' => require(__DIR__ . '/db.php'), // … a lot of other components skipped here … 'assetManager' => [ 'bundles' => (require __DIR__ . '/assets_compressed.php') ], ], 'extensions' => (require __DIR__ . '/../vendor/yiisoft/extensions.php') ];
We have already used separate configuration for database, compressed assets (from Chapter 8, Overall Behavior), and extensions.
It's probably the only place where an alternate syntax for the require()
PHP built-in method looks useful. The (require PATH)
notation explicitly expresses that we fetch something from PATH
and put it right inside the brackets, which is arguably easier to read than the usual function-call notation.
Using the same technique, you can separate the declarations of the custom parameters in the params
setting of the application. The Yii basic-application template uses this trick (see https://github.com/yiisoft/yii2/tree/master/apps/basic/config).
Configuration overriding can be raised to the next level if you take into account the array_merge_recursive()
PHP built-in method and the yiihelpersArrayHelper::merge()
method from the Yii framework. The ArrayHelper::merge()
static function is especially useful. Instead of combining values with identical keys, array_merge_recursive()
overrides the initial value with the new one.
How can it be used? Obviously, we can have the default application configuration in one file and deploy target-specific overrides in another file. Then the resulting configuration to be fed to the Yii web application instance will be constructed by ArrayHelper::merge()
, as follows:
// config/web.php, as we always had return yiihelpersArrayHelper::merge( (require "default.php"), (require "local.php") );
Inside the local.php
file, you maintain exactly the same structure as the default.php
file, which is a lot easier to do than constantly remembering which part of the configuration lies in what configuration snippet. Of course, some files, such as the extensions.php
file will be left as is. In case of the extensions.php
file, we don't have control over its contents anyway.
In Chapter 2, Making a Custom Application with Yii 2, we introduced a separate db.php
configuration snippet to conform to the Yii basic application template, without really explaining exactly why we needed to separate this configuration at all. In fact, such a separation is a rudimentary step to making a code base more portable between developers and deploy targets. On each of them you can have different db.php
scripts with credentials and the like. To achieve that, you cannot just commit this snippet to the version control system. However, such an approach has a serious drawback: each instance of the application should have a database configuration written manually from scratch. You need to communicate any common settings between developers in some other way than the code committed into repository, which means you cannot have any common settings committed to the repository, like maybe the custom database connection class or caching settings.
Let's apply the technique explained earlier to our code base. To do this, we need to make some really small changes:
overrides
inside the config
directory. It's more correct to name it layers
, though, but overrides
implies the meaning that the snippets will override each other, while layers
require additional understanding about the technique we are employing.overrides
create a file named base.php
, that will hold the basic configuration for both console and web applications on any deploy target.config/overrides/base.php
. They are as follows:return [ 'basePath' => realpath(__DIR__ . '/../../'), 'components' => [ 'db' => [ 'class' => 'yiidbConnection' ], ] ];
In the Yii advanced application template, there are separate subdirectories for the console and two web applications, so basePath
for them will be different. In our case, both applications share the basePath
setting.
We have initialized the database connection component with just the name of the class. The credentials will be provided in later overrides. We need the database connection in the console application as well as in the web application because of migrations.
Here are the steps to perform it:
web.php
configuration file to the config/overrides/web_base.php
file. This name is chosen because we will recreate the config/web.php
file later, and it's generally not so good to have two files in the code base sharing a name, even if they are in different folders.web_base.php
, we need to remove parts of the configuration already defined in the base configuration, which are just the basePath
and components.db
settings.extensions.php
and assets_compressed.php
configuration snippets inside web_base.php
, as we are one folder deeper now. It's up to you whether to move assets_compressed.php
somewhere, but it'll be a violation of the overrides concept to move this snippet to the overrides
directory.console.php
configuration file to the config/overrides/console_base.php
file. Similarly, remove the basePath
and components.db
settings from there.config/overrides/local.php
config file, with just the database connection settings in there as follows:return [ 'components' => [ 'db' => [ 'dsn' => 'mysql:host=localhost;dbname=crmapp', 'username' => 'root', 'password' => 'mysqlroot' ] ] ];
Regardless of how specific the local overrides, you need to have exactly the same structure of the configuration file as in the main Yii configuration. The whole idea of overrides is based on this sharing of structure.
config/web.php
and config/console.php
expected by our entry points. Here is how config/web.php
will look:return yiihelpersArrayHelper::merge( (require __DIR__ . '/overrides/base.php'), (require __DIR__ . '/overrides/web_base.php'), (require __DIR__ . '/overrides/local.php') );
We just merge the three configurations, and the order is crucial. Now, if you open the website in a browser, nothing should change, which is exactly what we need. All tests should still pass as well.
In the same manner, construct config/console.php
, except instead of the web_base.php
, we'll use console_base.php
.
These changes lead to important consequences. The main benefit is that we can now add to the version control system only the base and web base configuration overrides. The local override will be constructed on each deployment. To help developers and operation engineers, we can commit to the repository a specially prepared copy of local.php
, with all exact credentials replaced by some meaningful placeholders. Such configuration templates are usually named after the file they represent, with the -example
suffix appended, for example, local-example.php
. Here is how we can format such an example in our CRM application:
<?php /** * This is the example for local configuration overrides. * You must declare at least the database connection parameters. */ return [ 'components' => [ 'db' => [ 'dsn' => 'mysql:host=localhost;dbname=DB_NAME', 'username' => 'DB_USERNAME', 'password' => 'DB_PASSWORD' ] ] ];
The advanced application template from the Yii bundle already uses a similar trick with overrides but only for custom application parameters (see, for example, https://github.com/yiisoft/yii2/blob/master/apps/advanced/frontend/config/main.php). To look at a real-world example of elaborated multilevel configuration building, you can turn to the YiiBoilerplate project from Clevertech at https://github.com/clevertech/YiiBoilerplate. It's for Yii 1.x, but the concept is the same, albeit a lot more extended.
18.119.103.204