《Yii 1.1应用程序开发实例》

第十三章:部署

本章中,我们将涵括以下内容:

  • Changing the Yii directories layout
  • Moving an application out of webroot
  • Sharing the framework directory
  • Moving configuration parts into separate files
  • Using multiple configurations to simplify the deployment
  • mplementing and executing cron jobs
  • Maintenance mode

介绍

In this chapter, we will cover various tips which are especially useful on application deployment and when developing an application in a team, or when you just want to make your development environment more comfortable.

Changing the Yii directories layout

Yii has a pre-defined convention for directories layout. It allows us to significantly lower the learning curve but sometimes, the custom directory structure fits the project better.

In this recipe, we will rename a few directories and share common libraries around separate projects.

The plan is to:

  • Rename protected to app
  • Create a shared directory where we can store components shared across multiple applications
  • Move runtime out of app

准备工作

  • Get a framework copy from the Yii website
  • Set up the following directory structure:
/var/www/example/
    framework/
www/
  • Unpack the framework directory contents to /var/www/example/framework/

怎么做...

进行以下步骤:

  1. Go to the framework directory.
  2. Run yiic webapp /var/www/example/www/.
  3. Go to /var/www/example/www and rename protected to app.
  4. Replace all occurrences of protected with app in index.php and index-test. php.
    Now, we have a custom directory named protected.
  5. Create /var/www/shared.
  6. In your main.php config add:
// uncomment the following to define a path alias
Yii::setPathOfAlias('shared','/var/www/shared');
...
// This is the main Web application configuration. Any writable
// CWebApplication properties can be configured here.
return array(
    ...
 
    // autoloading model and component classes
    'import'=>array(
        ...
        'shared.*',
    ),

That is it. Now you can place your own components under /var/www/shared and the application will be aware of them. In addition, if you add the same settings to another application config, another application will be able to use these components as well.

  1. Move runtime from the /var/www/example/www/app directory to /var/www/ example/runtime.
  2. Modify your main.php config as follows:
return array(
    ...
    'runtimePath' => Yii::getPathOfAlias('system').'/../runtime/',
  1. That is it. Now the runtime directory is outside of the application directory.

它是如何工作的...

The application directory name and path are determined only at two places: index.php and index-test.php, so it is relatively easy to change these. We just need to update two bootstrap files after renaming the application directory.

When creating a shared directory, we define a custom path alias. It is a very convenient way of using additional directories if you are referring to them often:

Yii::setPathOfAlias('shared','/var/www/shared');

setPathOfAlias accepts two arguments. First is the name that we will use when setting options accepting paths, Yii::getPathOfAlias and Yii::import. Second is the actual path to the directory. As we want to use components transparently, we are adding shared.* to the list of application imports. This allows classes from the /var/www/shared directory to be loaded automatically.

The last path we change is a path to the runtime directory. For this case, Yii defines an application property named runtimePath. We can set it to change the path used as follows:

'runtimePath' => Yii::getPathOfAlias('system').'/../runtime/',

As we want to place runtime in the same file structure level where the framework directory is, we get the framework directory path by using getPathOfAlias and then append the relative path to our runtime directory.

An application defines some other properties allowing you to change the extensions path, translations path, and modules path. Other paths such as views path or cache files path could be configured by changing another component's properties. For a view, it is CController::viewPath and for cache (in case of using file cache), it is CFileCache::cachePath.

还有更多...

In order to learn more about Yii directory paths, refer to the following URLs:

  • http://www.yiiframework.com/doc/api/YiiBase#getPathOfAlias-detail
  • http://www.yiiframework.com/doc/api/YiiBase#setPathOfAlias-detail
  • http://www.yiiframework.com/doc/api/YiiBase#import-detail
  • http://www.yiiframework.com/doc/api/CApplication#setExtensionPath-detail
  • http://www.yiiframework.com/doc/api/ CApplication#setRuntimePath-detail
  • http://www.yiiframework.com/doc/api/ CApplication#setLocaleDataPath-detail
  • http://www.yiiframework.com/doc/api/CModule#setModulePath-detail
  • http://www.yiiframework.com/doc/api/CController#viewPath-detail

另请参阅

  • The recipe named Moving an application out of webroot in this chapter

Moving an application out of webroot

By default, when generating web application by using yiic webapp command, Yii puts both the index.php and protected directory in a single place that is typically the server's webroot. It allows running Yii in very restricted environments, but for security purposes and ease of development, it is better to keep your code out of webroot if it can be kept out of it.

In this recipe, we will see how to move a Yii application out of the server's webroot located at /var/www/website/www/.

准备工作

  • Copy the framework directory to /var/www/website/
  • Go to /var/www/website/framework/ and run yiic webapp /var/www/website/www/
  • You should get the default web application files under /var/www/website/www/

怎么做...

进行以下步骤:

  1. First, we need to move /var/www/website/www/protected/ to /var/www/ website/protected/. As the path was changed, the application will fail to run now. Both index.php and index-test.php need some fixing. The index.php file has the following content:
// change the following paths if necessary
$yii=dirname(__FILE__).'/../framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';
 
// remove the following lines when in production mode
defined('YII_DEBUG') or define('YII_DEBUG',true);
// specify how many levels of call stack should be shown
    in each log message
    
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
 
require_once($yii);
Yii::createWebApplication($config)->run();
  1. There are two paths defined: path to the framework directory that was not changed and path to config that was changed. Let's update the latter as follows:
$config=dirname(__FILE__).'/../protected/config/main.php';
  1. We need to do the same in index-test.php:
$config=dirname(__FILE__).'/../protected/config/test.php';
  1. That is it. Now try to run the application and it should show a standard welcome screen, as shown in the following screenshot:
  1. Now, we need to fix one more path in protected/yiic.php. We do this as follows:
// change the following paths if necessary
$yiic=dirname(__FILE__).'/../framework/yiic.php';
$config=dirname(__FILE__).'/config/console.php';
require_once($yiic);

它是如何工作的...

A Yii application can be moved to any place in a filesystem where we want it to be. The only thing you should correct is paths in index.php and index-test.php. This simple move gives you slightly better security as no application code will be executed directly, and there will be no leaks in the source code through version control meta files, and so on. Therefore, if your production environment allows moving the application code out of webroot, you certainly should consider doing it.

还有更多...

http://www.smashingmagazine.com/2009/09/25/svn-strikes-back-a- serious-vulnerability-found/

另请参阅

  • The recipe named Sharing the framework directory in this chapter

Sharing the framework directory

If you run multiple Yii projects on a single web server, then you can consider sharing the framework code between the projects. This will save some disk space and will require less work when you upgrade your applications to a new framework version.

准备工作

Copy the framework directory contents to /var/www/common/yii/latest.

怎么做...

进行以下步骤:

  1. Go to /var/www/common/yii/latest and run yiic webapp /var/www/website1/www/.
  2. Go to /var/www/common/yii/latest and run yiic webapp /var/www/website2/www/.
  3. You should get the default web application files under /var/www/website1/www/and /var/www/website2/www/.
  4. That is it. Try to run applications to make sure that everything works.

它是如何工作的...

Using yiic webapp from the single framework copy will create applications referencing to this framework instance. We have two applications using the same framework copy, so when upgrading framework, we should only replace a single directory's contents.

If you have existing applications, then you can do the same by editing their index.php and index-test.php files, so $yii values will be as follows:

$yii='/var/www/common/yii/latest/yii.php';

还有更多...

As the new Yii version can possibly introduce some backwards incompatible changes (typically, there are no such changes in minor releases), it is good to have a way to quickly roll everything back. For this purpose, you have to keep several framework versions under / var/www/common/yii/ (for example: 1.1.8 and 1.1.7). When upgrading an application to 1.1.8, you are changing path to yii.php in index.php and index-test.php for a single application and testing for regressions. If there are any, you can either fix them or roll everything back by quickly changing a path back to 1.1.7. If everything is fine, then you can safely move on to test the next application.

另请参阅

  • The recipe named Moving application out of webroot in this chapter

Moving configuration parts into separate files

By default, a Yii application stores the entire web application configuration in a single file named protected/config/main.php. The same goes for the console application. It is good for both learning and small web applications where keeping everything inside of a single config file gives a developer the ability to quickly overview the whole application's settings. When we develop something bigger, we may face some inconvenience, such as the following:

  • The configuration file becomes too bloated if there are many things to configure. Moreover, in a big application, there are typically many components used.
  • If we need to adjust some settings, then we most probably end up repeating changes in both the web application config and console application config.

准备工作

Create a fresh application by using yiic webapp.

怎么做...

进行以下步骤:

  1. We will review the default config first to identify parts we will reuse, as well as parts that will most probably be too large to have in a single file:
<?php
// uncomment the following to define a path alias
// Yii::setPathOfAlias('local','path/to/local-folder');
// This is the main Web application configuration. Any writable
// CWebApplication properties can be configured here.
return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'My Web Application',
    // preloading 'log' component
    'preload'=>array('log'),
    // autoloading model and component classes
    'import'=>array(
        'application.models.*',
        'application.components.*',
    ),
 
    'modules'=>array(
        // uncomment the following to enable the Gii tool
        /*
        'gii'=>array(
            'class'=>'system.gii.GiiModule',
            'password'=>'Enter Your Password Here',
            //If removed, Gii defaults to localhost only.
                Edit carefully to taste.
            'ipFilters'=>array('127.0.0.1','::1'),
        ),
        */
    ),
 
    // application components
    'components'=>array(
        'user'=>array(
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
        ),
        // uncomment the following to enable URLs in path-format
        /*
        'urlManager'=>array(
            'urlFormat'=>'path',
            'rules'=>array(
                '<controller:\w+>/<id:\d+>'=>'<controller>/view',
                '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
                '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
            ),
        ),
        */
        'db'=>array(
            'connectionString' =>
            'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
        ),
        // uncomment the following to use a MySQL database
        /*
        'db'=>array(
            'connectionString' =>
            'mysql:host=localhost;dbname=testdrive',
            'emulatePrepare' => true,
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ),
        */
        'errorHandler'=>array(
            // use 'site/error' action to display errors
            'errorAction'=>'site/error',
        ),
        'log'=>array(
            'class'=>'CLogRouter',
            'routes'=>array(
                array(
                    'class'=>'CFileLogRoute',
                    'levels'=>'error, warning',
                ),
                  // uncomment the following to show log messages on web pages
                /*
                array(
                    'class'=>'CWebLogRoute',
                ),
                */
            ),
        ),
    ),
    // application-level parameters that can be accessed
    // using Yii::app()->params['paramName']
    'params'=>array(
        // this is used in contact page
        'adminEmail'=>'webmaster@example.com',
    ),
);

The imports list and module configuration are typically not too large. The same goes for the most components configuration. What can grow with the application complexity are the urlManager component routes and application-level parameters. As for reusing, we will probably need the same imports, database connection, and application-level parameters for both the web application and console application.

  1. Now, we will create the following config files under protected/configs:
  • routes.php
  • params.php
  • import.php
  • db.php
  1. Now we need to move the corresponding sections of the main.php into separate files. We do this as follows:
// uncomment the following to define a path alias
// Yii::setPathOfAlias('local','path/to/local-folder');
// This is the main Web application configuration. Any writable
// CWebApplication properties can be configured here.
return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'My Web Application',
    // preloading 'log' component
    'preload'=>array('log'),
    // autoloading model and component classes
    'import'=>require(dirname(__FILE__).'/import.php'),
    'modules'=>array(
        // uncomment the following to enable the Gii tool
        /*
        'gii'=>array(
            'class'=>'system.gii.GiiModule',
            'password'=>'Enter Your Password Here',
            // If removed, Gii defaults to localhost only.
                Edit carefully to taste.
            'ipFilters'=>array('127.0.0.1','::1'),
        ),
        */
    ),
    // application components
    'components'=>array(
        'user'=>array(
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
        ),
        // uncomment the following to enable URLs in path-format
        'urlManager'=>array(
            'urlFormat'=>'path',
            'rules'=>require(dirname(__FILE__).'/routes.php'),
        ),
        'db'=>require(dirname(__FILE__).'/db.php'),
        'errorHandler'=>array(
            // use 'site/error' action to display errors
            'errorAction'=>'site/error',
        ),
        'log'=>array(
            'class'=>'CLogRouter',
            'routes'=>array(
                array(
                    'class'=>'CFileLogRoute',
                    'levels'=>'error, warning',
                ),
                  // uncomment the following to show log messages    on web pages
                /*
                array(
                    'class'=>'CWebLogRoute',
                ),
                */
            ),
        ),
    ),
    // application-level parameters that can be accessed
    // using Yii::app()->params['paramName']
    'params'=>require(dirname(__FILE__).'/params.php'),
);
  1. Each new config will contain the same values that were there in the main config. For example, protected/configs/params.php will contain the following:
<?php
return array(
    // this is used in contact page
    'adminEmail'=>'webmaster@example.com',
);
  1. Now we need to change the console application protected/configs/console. php. We do this as follows:
// This is the configuration for yiic console application.
// Any writable CConsoleApplication properties can be configured here.
return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'My Console Application',
 
    // autoloading model and component classes
    'import'=>require(dirname(__FILE__).'/import.php'),
    // application components
    'components'=>array(
        'db'=>require(dirname(__FILE__).'/db.php'),
    ),
    // application-level parameters that can be accessed
    // using Yii::app()->params['paramName']
    'params'=>require(dirname(__FILE__).'/params.php'),
);
  1. That is it. Now we have separate configuration files for imports, database configuration, application routes, and application parameters.

它是如何工作的...

The preceding technique relies on the fact that Yii configuration files are native PHP files with arrays:

<?php
    return array(...);

When we use the require construct:

'db'=>require(dirname(__FILE__).'/db.php')

It reads the file specified, and, if there is a return statement inside this file, it returns a value. Therefore, moving a part out of the main configuration file into a separate file requires creating a separate file, moving the configuration part into it right after the return statement, and using require in the main configuration file.

If separate applications (in our example, these are web applications and console applications) require some common configuration parts, then we can use require to move these into a separate file.

还有更多...

In order to learn more about PHP require and include statements, refer to the following URLs:

  • http://php.net/manual/en/function.require.php
  • http://php.net/manual/en/function.include.php

另请参阅

  • The recipe named Using multiple configurations to simplify the deployment in this chapter

Using multiple configurations to simplify the deployment

In some cases, it is handy to use different configuration files for different cases. For example, we can use different configuration files for the development environment and production environment.

In this recipe, we will see how to choose a configuration file automatically and how to implement the configuration inheritance.

准备工作

Create a fresh application by using yiic webapp.

怎么做...

进行以下步骤:

  1. We will assume that we are using http://example.com/ as a production URL and http://example.local/ as a development URL. Given this fact, we can choose the appropriate config from index.php as follows:
// change the following paths if necessary
$yii=dirname(__FILE__).'/../framework/yii.php';
if($_SERVER['HTTP_HOST']=='example.com')
{
    $config=dirname(__FILE__).'/../protected/config/production.php';
}
else {
    // remove the following lines when in production mode
    defined('YII_DEBUG') or define('YII_DEBUG',true);
    // specify how many levels of call stack should be shown in each log message
    defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
        
    $config=dirname(__FILE__).'/../protected/config/development.php';
}
require_once($yii);
Yii::createWebApplication($config)->run();
  1. The plan ahead is to leave all common configurations in main.php and override the environment-specific settings in development.php and production. php. Therefore, main.php stays the same. We put the following in protected/ configs/development.php as follows:
<?php
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(
        'modules'=>array(
            'gii'=>array(
                'class'=>'system.gii.GiiModule',
                'password'=>false,
            ),
        ),
        'components'=>array(
            'db'=>array(
                'class'=>'system.db.CDbConnection',
                'connectionString'=>'mysql:host=localhost;dbname=example',
                'username'=>'root',
                'password'=>'',
                'charset'=>'utf8',
                'enableProfiling'=>true,
                'enableParamLogging'=>true,
            ),
            'log'=>array(
                'class'=>'CLogRouter',
                'routes'=>array(
                    array(
                        'class'=>'CProfileLogRoute',
                    ),
                ),
            ),
        ),
    )
);
  1. In addition, put the following in protected/configs/production.php as follows:
<?php
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(
        'components'=>array(
            'db'=>array(
                'class'=>'system.db.CDbConnection',
                'connectionString'=>'mysql:host=localhost;dbname=example',
                'username'=>'example',
                'password'=>'2WXyVNb4dBSEK3HW',
                'charset'=>'utf8',
                'schemaCachingDuration'=>60*60,
            ),
            'cache'=>array(
                'class'=>'CFileCache',
            ),
        ),
    )
);
  1. That is it. Now we can just upload files to the production server that runs http://example.com/ and the application will use the production.php config that inherits all settings from main.php, overrides some of them, and adds some more settings.

它是如何工作的...

When we create a web application instance inside of index.php, it is possible to pass a single argument to Yii::createWebApplication. This argument is a path to the application configuration file. Given this fact, we can vary the path to this file based on some kind of criteria. In our case, it is the name of the host where the application is running:

if($_SERVER['HTTP_HOST']=='example.com')
{
    $config=dirname(__FILE__).'/../protected/config/production.php';
}
else if($_SERVER['HTTP_HOST']=='example.local')
{
    // remove the following lines when in production mode
    defined('YII_DEBUG') or define('YII_DEBUG',true);
    // specify how many levels of call stack should be shown in each log message
    defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
    $config=dirname(__FILE__).'/../protected/config/development.php';
}

If host equals example.com, then we use the production config. Else, we use the development config and additionally, turn on the debugging.

You can use virtually anything to choose the config file. For example, you can run the application in a debug mode at the production server if there is a cookie with a specific name and value.

As most of the development and production settings, such as application parameters and routes, will stay the same, we leave these in main.php. Moreover, as Yii configuration files are PHP arrays, we can use CMap::mergeArray to implement the configuration inheritance as follows:

return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(...)
);

还有更多...

In order to learn more about how exactly config files are merged, refer to the following URL:

http://www.yiiframework.com/doc/api/CMap#mergeArray

另请参阅

  • The recipe named Moving configuration parts into separate files in this chapter

Implementing and executing cron jobs

Sometimes, an application requires some background tasks such as re-generating a sitemap or refreshing statistics. A common way to implement this is by using cron jobs. When using Yii, there are two ways to do it which are as follows:

  1. Emulate the browser to call the web application controller action.
  2. Use the command line command to run as a job.

In this recipe, we will see how to implement both. For our recipe, we will implement writing the current timestamp into a timestamp.txt file under the protected directory.

准备工作

Create a fresh application by using yiic webapp.

怎么做...

进行以下步骤:

  1. Create protected/controllers/CronController.php as follows:
<?php
class CronController extends CController
{
    public function actionIndex()
    {
        $filename = Yii::getPathOfAlias("application")."/timestamp.txt";
        file_put_contents($filename, time());
    }
}
  1. Now we need a way to call it. As it is a web application controller, we need to somehow emulate a browser. In Linux, you can use one of the following commands in your crontab:
GET http://example.com/index.php?r=cron
wget -O - http://example.com/index.php?r=cron
lynx --dump http://example.com/index.php?r=cron >/dev/null

When we use a controller in this way, we need to make sure that it is used only as a cron job. For example, we can check for a value of a specific $_GET variable.

  1. Create protected/commands/CronCommand.php as follows:
<?php
class CronCommand extends CConsoleCommand
{
    public function run($args)
    {
        $filename = Yii::getPathOfAlias("application")."/timestamp.txt";
  
        file_put_contents($filename, time());
    }
}
  1. We can use the following in the crontab to execute it:
/path/to/./yiic cron

Note that the full path is required to be specified in cron.

它是如何工作的...

GET, wget, and lynx fetch a page through HTTP, so we can use these to trigger the normal web application execution. This method has its pros and cons. The main pro is that we are executing a web application, so the environment is the same as in all other application parts. The main con is that it is not completely secure. Even if we use $_GET variable as a password, there is no guarantee that it will remain undisclosed forever.

The second method is much more secure because the console command can be executed only if one has an SSH access to the server. The con is that the environment is different from the web application.

还有更多...

Another way to solve a problem is to use a message queue. Most implementations will deal with concurrency issues for you and will allow you to do the processing almost immediately if there are not too many requests. Implementations to check are:

  • http://www.rabbitmq.com/
  • http://kr.github.com/beanstalkd/
  • http://activemq.apache.org/
  • http://gearman.org/
  • https://github.com/s0enke/dropr/
  • http://www.zeromq.org/
进一步阅读

In order to learn more about Yii console applications, refer to the following URL:

http://www.yiiframework.com/doc/guide/en/topics.console

另请参阅

  • The recipe named Creating CLI commands in Chapter 8, Extending Yii

Maintenance mode

Sometimes, there is a need to fine tune some application settings or restore a database from a backup. When working on tasks such as these, it is not desirable to allow everyone to use the application because it can lead to losing the recent user messages or showing the application implementation details.

In this recipe, we will see how to show everyone except the developer a maintenance message.

准备工作

Create a fresh application by using yiic webapp.

怎么做...

进行以下步骤:

  1. First, we need to create protected/controllers/MaintenanceController. php. We do this as follows:
<?php
class MaintenanceController extends CController
{
    public function actionIndex()
    {
        $this->renderPartial("index");
    }
}
  1. Then, we create a view named protected/views/maintenance/index.php as follows:
<!doctype html>
    <head>
        <meta charset="utf-8" />
        <title><?php echo CHtml::encode(Yii::app()->name)?>
            is under maintenance</title>
    </head>
    <body>
        <h1><?php echo CHtml::encode(Yii::app()->name)?> is under maintenance</h1>
        <p>We'll be back soon. If we aren't back for too long,
        please drop a message to 
        <?php echo Yii::app()->params['adminEmail']?>.</p>
        <p>Meanwhile, it's a good time to get a cup of coffee,
        to read a book or to check email.</p>
    </body>
  1. Now we need to add a single line of code to protected/config/main.php as follows:
return array(
    'catchAllRequest'=>file_exists(
        dirname(__FILE__).'/.maintenance') && 
            !(isset($_COOKIE['secret']) && $_COOKIE['secret']=="password") ?
                array('maintenance/index') : null,
        ...
  1. That is it. Now in order to go into the maintenance mode, you need to create a file named .maintenance in protected/config/.

In order to get it back to normal, you just need to delete it. To view the website in the maintenance mode, you can create a cookie named secret with value equal to password.

它是如何工作的...

A Yii web application offers a way to intercept all possible requests and route these to a single controller action. You can do this by setting CWebApplication::catchAllRequests to an array containing application route as follows:

'catchAllRequest'=>array('maintenance/index'),

The maintenance controller itself is nothing special; it just renders a view with a text.

We need an easy way to turn the maintenance mode on and off. As the application config is a regular PHP file, we can achieve it with a simple check for the file existence as follows:

file_exists(dirname(__FILE__).'/.maintenance')

In addition, we check for the cookie value to be able to override the maintenance mode. We do this as follows:

!(isset($_COOKIE['secret']) && $_COOKIE['secret']=="password")

还有更多...

In order to learn more about how to catch all requests in a Yii application and check the production ready solution for maintenance, refer to the following URLs:

  • http://www.yiiframework.com/doc/api/ CWebApplication/#catchAllRequest-detail
  • https://github.com/karagodin/MaintenanceMode

另请参阅

  • The recipe named Moving configuration parts into separate files in this chapter
  • The recipe named Using multiple configurations to simplify the deployment in this chapter
评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com