Yii Application Development Cookbook Second Edition


Yii Application Development Cookbook Second Edition A Cookbook covering both practical Yii application development tips and the most important Yii features Alexander Makarov BIRMINGHAM - MUMBAI Yii Application Development Cookbook Second Edition Copyright © 2013 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. First published: August 2011 Second edition: April 2013 Production Reference: 1150413 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-78216-310-7 www.packtpub.com Cover Image by Alexander Makarov (sam@rmcreative.ru) Credits Author Alexander Makarov Reviewers Maurizio Domba Thomas Jantz Acquisition Editor Usha Iyer Lead Technical Editor Joel Noronha Technical Editors Hardik B. Soni Ankita Meshram Project Coordinator Kranti Berde Proofreaders Maria Gould Paul Hindle Lawrence A. Herman Indexer Hemangini Bari Graphics Ronak Dhruv Production Coordinator Nilesh R. Mohite Cover Work Nilesh R. Mohite About the Author Alexander Makarav is an experienced engineer from Russia and has been a Yii framework core team member since 2010. Before joining the Yii core team, he participated in the CodeIgniter community growth in Russia. In 2009, he finished the Russian translation of the framework documentation and created the Russian community website. In 2012, he released the Russian version of the book along with Russian community members. In the same year, he was the technical reviewer for three more books: ff The Yii Book: Developing Web Applications Using the Yii PHP Framework, Larry Ullman ff Web Application Development with Yii and PHP, Jeff Winesett ff Yii Rapid Application Development Hotshot, Lauren O'Meara and James Hamilton In his free time, Alexander writes technical blog at http://rmcreative.ru/, speaks at conferences, and enjoys movies, music, traveling, photography, and languages. He currently resides in Voronezh, Russia with his beloved wife and daughter. About the Reviewers Maurizio Domba is a frontend and backend web developer with over 20 years of professional experience in computer programming and 10 years in web development. He is part of the Yii core development team since August 2010 and is an active member of the Yii community. At the moment he is developing intranet web applications for an export-import enterprise and working on other international projects, always trying to help others to improve their code and project usability. When not programming the Web, he is programming his wife and kids, always with a smile on his face, open-hearted and open-minded. He loves climbing, martial arts, meditation, and salsa. Thomas Jantz brings his background in language and art to his career as a web application development consultant at Plum Flower Software. His projects include an emphasis on rapid application development and user experience. www.PacktPub.com Support files, eBooks, discount offers and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at service@packtpub.com for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters and receive exclusive discounts and offers on Packt books and eBooks. TM http://PacktLib.PacktPub.com Do you need instant solutions to your IT questions? PacktLib is Packt's online digital book library. Here, you can access, read and search across Packt's entire library of books.  Why Subscribe? ff Fully searchable across every book published by Packt ff Copy and paste, print and bookmark content ff On demand and accessible via web browser Free Access for Packt account holders If you have an account with Packt at www.PacktPub.com, you can use this to access PacktLib today and view nine entirely free books. Simply use your login credentials for immediate access. Table of Contents Preface 1 Chapter 1: Under the Hood 7 Introduction 7 Using getters and setters 7 Using Yii events 10 Using import and autoloading 17 Using exceptions 20 Configuring components 24 Configuring widget defaults 27 Using Yii core collections 28 Working with requests 32 Chapter 2: Router, Controller, and Views 37 Introduction 38 Configuring URL rules 38 Generating URLs by path 41 Using regular expressions in URL rules 45 Creating URL rules for static pages 48 Providing your own URL rules at runtime 51 Using a base controller 55 Using external actions 57 Displaying static pages with CViewAction 61 Using flash messages 63 Using the controller context in a view 64 Reusing views with partials 66 Using clips 68 Using decorators 70 Defining multiple layouts 71 Paginating and sorting data 73 ii Table of Contents Chapter 3: AJAX and jQuery 77 Introduction 77 Loading a block through AJAX 77 Managing assets 83 Including resources in the page 88 Working with JSON 92 Passing configuration from PHP to JavaScript 95 Handling variable number of inputs 98 Rendering content at the client side 105 Chapter 4: Working with Forms 119 Introduction 119 Writing your own validators 119 Uploading files 122 Adding CAPTCHA 126 Customizing CAPTCHA 131 Creating a custom input widget with CInputWidget 134 Chapter 5: Testing Your Application 139 Introduction 139 Setting up the testing environment 139 Writing and running unit tests 143 Using fixtures 149 Testing the application with functional tests 155 Generating code coverage reports 160 Chapter 6: Database, Active Record, and Model Tricks 165 Introduction 166 Getting data from a database 166 Defining and using multiple DB connections 172 Using scopes to get models for different languages 175 Processing model fields with AR event-like methods 178 Applying markdown and HTML 181 Highlighting code with Yii 183 Automating timestamps 189 Setting up an author automatically 191 Implementing single table inheritance 193 Using CDbCriteria 197 Chapter 7: Using Zii Components 199 Introduction 199 Using data providers 199 Using grids 206 iii Table of Contents Using lists 214 Creating custom grid columns 219 Chapter 8: Extending Yii 225 Introduction 225 Creating model behaviors 226 Creating components 232 Creating reusable controller actions 236 Creating reusable controllers 239 Creating a widget 243 Creating CLI commands 245 Creating filters 249 Creating modules 251 Creating a custom view renderer 258 Making extensions distribution-ready 263 Chapter 9: Error Handling, Debugging, and Logging 267 Introduction 267 Using different log routes 267 Analyzing the Yii error stack trace 274 Logging and using the context information 277 Implementing your own smart 404 handler 281 Chapter 10: Security 287 Introduction 287 Using controller filters 287 Using CHtml and CHtmlPurifier to prevent XSS 292 Preventing SQL injections 296 Preventing CSRF 301 Using RBAC 305 Chapter 11: Performance Tuning 313 Introduction 313 Following best practices 313 Speeding up session handling 317 Using cache dependencies and chains 321 Profiling an application with Yii 327 Leveraging HTTP caching 337 Chapter 12: Using External Code 343 Introduction 343 Using Zend Framework from Yii 343 Customizing the Yii autoloader 348 Using Kohana inside Yii 353 iv Table of Contents Using PEAR inside Yii 361 Using Composer with Yii 363 Chapter 13: Deployment 367 Introduction 367 Changing the Yii directory layout 367 Moving an application out of webroot 370 Sharing the framework directory 372 Moving configuration parts into separate files 374 Using multiple configurations to simplify the deployment 378 Implementing and executing cron jobs 381 Maintenance mode 383 Index 387 Preface Yii is a very flexible and high-performance application development framework written in PHP. It helps building web applications, from small to large-scale enterprise applications. The framework name stands for Yes It Is (Yii). This is often the accurate and most concise response to inquiries from those new to Yii such as: Is it fast? Is it secure? Is it professional? Is it right for my next project? But the answer is an unequivocal, yes it is! This cookbook contains 13 independent chapters full of recipes that will show you how to use Yii efficiently. You will learn about the hidden framework gems, using core features, creating your own reusable code base, using test-driven development, and many more topics that will bring your knowledge to a whole new level! What this book covers Chapter 1, Under the Hood, provides information about the most interesting Yii features hidden under the hood: events, import, autoloading, exceptions, component, widget configuration, and many more. Chapter 2, Router, Controller, and Views, is about handy things concerning the Yii URL router, controllers, and views: URL rules, external actions and controllers, view clips, decorators, and more. Chapter 3, AJAX and jQuery, focuses on the Yii's client side that is built with jQuery—the most widely used JavaScript library out there. It is very powerful and easy to learn and use. This chapter focuses on Yii-specific tricks rather than jQuery itself. Chapter 4, Working with Forms, shows how Yii makes working with forms a breeze and documentation on it is almost complete. Still, there are some areas that need clarification and examples. Some of the topics covered in this chapter are creating validators and input widgets, uploading files, and using and customizing CAPTCHA. Chapter 5, Testing Your Application, covers unit testing, functional testing, and generating code coverage reports. Recipes follow a test-driven development approach. You will write tests for several small applications and then implement functionality. Preface 2 Chapter 6, Database, Active Record, and Model Tricks, is about working with databases efficiently, when to use models and when not to, how to work with multiple databases, how to automatically pre-process Active Record fields, and how to use powerful database criteria. Chapter 7, Using Zii Components, covers data providers, grids, and lists: How to configure sorting and searching, how to use grids with multiple related models, how to create your own column types, and more. Chapter 8, Extending Yii, shows not only how to implement your own Yii extension but also how to make your extension reusable and useful for the community. In addition, we will focus on many things you should do to make your extension as efficient as possible. Chapter 9, Error Handling, Debugging, and Logging, reviews logging, analyzing the exception stack trace, and own error handler implementation. Chapter 10, Security, provides information about keeping your application secure according to the general web application security principle "filter input, escape output." We will cover topics such as creating your own controller filters, preventing XSS, CSRF, and SQL injections, escaping output, and using role-based access control. Chapter 11, Performance Tuning, shows how to configure Yii to gain extra performance. You will learn a few best practices for developing an application that will run smoothly until you have very high loads. Chapter 12, Using External Code, focuses on using third-party code with Yii. We will use Zend Framework, Kohana, and PEAR but you will be able to use any code after learning how it works. Chapter 13, Deployment, covers various tips that are especially useful on application deployment, when developing an application in a team, or when you just want to make your development environment more comfortable. What you need for this book In order to run the examples in this book, the following software will be required: ff Web server: The 2.x version of Apache web server is preferred. Other versions and web servers will work too, but configuration details are not provided. ff Database server: The database server that can be used is MySQL 4+ with InnoDB support (MySQL 5 or higher is recommended). ff PHP: The PHP 5.2 or PHP 5.3 version can be used (PHP 5.3 recommended). ff Yii: The latest Yii version can be used (1.1.x is recommended). Preface 3 Additionally, the following tools are not strictly required but are used for specific recipes: ff PHPUnit ff Xdebug ff Selenium RC ff PEAR ff Smarty ff memcached Who this book is for If you are a developer with a good knowledge of PHP5, are familiar with the basics of Yii, have checked its definitive guide, and have tried to develop applications using Yii, then this book is for you. Knowledge of the object-oriented approach and MVC pattern will be a great advantage as Yii uses these extensively. Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles are shown as follows: "To declare an event in your CComponent child class, you should add a method with a name starting with on." A block of code is set as follows: defined('YII_DEBUG') or define('YII_DEBUG', false); defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 0); $yii=dirname(__FILE__).'/../framework/yii.php'; $config=dirname(__FILE__).'/../app/config/production.php'; require($yii); Yii::createWebApplication($config)->run(); Any command-line input or output is written as follows: cd path/to/protected/tests phpunit unit/BBCodeTest.php Preface 4 New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "Now, go to the Gii controller generator and enter SecureController into the Base Class field." Warnings or important notes appear in a box like this. Tips and tricks appear like this. Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send the author feedback about the book, simply fill in the form at http://yiicookbook.org/feedback. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide at www.packtpub.com/authors. Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase. Downloading the example code To get the example code files for this book visit http://yiicookbook.org/code. Errata Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code—we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://yiicookbook.org/feedback, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on the book website at http://yiicookbook.org/errata. Preface 5 Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works, in any form, on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at copyright@packtpub.com with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content. Questions You can contact the book's author at http://yiicookbook.org/feedback. if you are having a problem with any aspect of the book, and he will do his best to address it. 1 Under the Hood In this chapter, we will cover: ff Using getters and setters ff Using Yii events ff Using import and autoloading ff Using exceptions ff Configuring components ff Configuring widget defaults ff Using Yii core collections ff Working with requests Introduction In this chapter, we will cover the most interesting Yii features that are hidden "under the hood". These are mostly described in the framework API, but since they are not mentioned in the official guide (http://www.yiiframework.com/doc/guide/) or only mentioned very briefly, only experienced Yii developers usually use these. However, the features described here are relatively simple and using them makes development with Yii much more fun and productive. Using getters and setters Yii has many features that came from other languages, such as Java or C#. One of them is defining properties with getters and setters for any of the classes extended from CComponent (that is, virtually any Yii class). Under the Hood 8 From this recipe, you will learn how to define your own properties using getters and setters, how to make your properties read-only, and how to hide custom processing behind native PHP assignments. How to do it... 1. As PHP does not have properties at the language level, we can only use getters and setters in the following way: class MyClass { // hiding $property private $property; // getter public function getProperty() { return $this->property; } // setter public function setProperty($value) { $this->property = $value; } } $object = new MyClass(); // setting value $object->setProperty('value'); // getting value echo $object->getProperty(); 2. This syntax is very common in the Java world but it is a bit long to use in PHP. Still, we want to use the same functionality that C# properties gives us: calling getters and setters like class members ($model->property instead of $model- >getProperty()). With Yii, we can do it in the following way: // extending CComponent is necessary class MyClass extends CComponent { private $property; public function getProperty() { Chapter 1 9 return $this->property; } public function setProperty($value) { $this->property = $value; } } $object = new MyClass(); $object->property = 'value'; // same as $object-> setProperty('value'); echo $object->property; // same as $object->getProperty(); 3. Using this feature, you can make properties read-only or write-only while keeping the simple PHP syntax as follows: class MyClass extends CComponent { private $read = 'read only property'; private $write = 'write only property'; public function getRead() { return $this->read; } public function setWrite($value) { $this->write = $value; } } $object = new MyClass(); // gives us an error since we are trying to write // to read-only property. Note that there's no setRead setter // method. $object->read = 'value'; // echoes 'read only property' echo $object->read; // gives us an error since we are trying to read // to write-only property. Note that there's no getWrite getter // method. echo $object->write; // writes 'value' to private $write $object->write = 'value'; Under the Hood 10 Yii uses this technique extensively because almost everything is a component. For example, when you call Yii::app()->user->id to get the currently logged in user ID, what's really called is Yii::app()->getUser()->getId(). How it works... To use getters and setters like properties, CComponent uses the PHP magic methods: __ get, __set, __isset, and __unset (http://php.net/manual/en/language.oop5. magic.php). The following example shows what Yii 1.1 CComponent::__get looks like: public function __get($name) { $getter='get'.$name; if(method_exists($this,$getter)) return $this->$getter(); … This magic PHP method intercepts all calls to missing real properties, so when we call $myClass->property, it receives property as the $name parameter. If a method named getProperty exists, then PHP uses its return value as a property value. There's more... For further information, refer to the following URL: http://www.php.net/manual/en/language.oop5.overloading.php#language. oop5.overloading.members See also ff The Configuring components recipe Using Yii events Most Yii classes are extended from CComponent, which allows us to achieve great application flexibility by using events. An event is a message indicating that the application did something. We can register several event handlers that will react to certain event types. A handler can get parameters from an event it works with and react accordingly. Using events allows us to achieve great application flexibility. In this recipe, you will learn how to declare and use both predefined and custom events in your application. Chapter 1 11 How to do it... To declare an event in your CComponent child class, you should add a method with a name starting with on. For example, if you add the onRegister method, you will get a corresponding event declared. A method used to declare an event becomes the default event handler. Typically, events are used like this: ff Declare an event by adding a corresponding method ff Attach one or multiple event handlers ff The component raises an event by using the CComponent::raiseEvent method ff All subscribed handlers are called automatically Let's look at how we can attach an event handler to an event. To achieve it, we can use the CComponent::attachEventHandler method. It accepts the following two parameters: ff $name: Event name ff $handler: Event handler; a standard PHP callback should be used In PHP, we have several ways to define a callback as follows: ff Use a global function and just pass its name as a string, such as 'my_function'. ff Use a static class method. You should pass an array: array('ClassName', 'staticMethodName'). ff Use an object method: array($object, 'objectMethod'). ff Create and pass anonymous function using create_function as follows: $component->attachEventHandler('onClick', create_function('$event', 'echo "Click!";')); ff Since PHP 5.3, you can use anonymous functions without create_function: $component->attachEventHandler('onClick', function($event){ echo "Click!"; }); When you use CComponent::attachEventHandler, event handler is added to the end of the handlers list. Under the Hood 12 ff To keep your code shorter, you can use component properties to manage event handlers as follows: $component->onClick=$handler; // or: $component->onClick->add($handler); ff To manage event handlers more precisely, you can get the handlers' list (CList) using CComponent::getEventHandlers and work with it. For example, you can attach an event handler the same way as with attachEventHandler using the following code: $component->getEventHandlers('onClick')->add($handler); ff To add an event handler to the beginning of the handlers' list, use the following code: $component->getEventHandlers('onClick')->insertAt(0, $handler); ff To delete a particular handler you can use CComponent::detachEventHandler as follows: $component->detachEventHandler('onClick', $handler); ff Alternatively, get a list of handlers as shown earlier and delete handlers from it. CComponent::hasEvent checks if the event specified is defined in the component. CComponent::hasEventHandler checks if there are handlers attached to the event specified. As we now know how to define and use handlers, let's review some real life examples as follows: ff It is a common practice to compress your application output using gzip to save client bandwidth and speed up page loading time. If you have full access to your server, then you can configure it to do so, but in some environments such as shared hosting, you don't. ff Fortunately, PHP can gzip the application output using output buffering and the ob_gzhandler function. In order to do so, we should start buffering the output when the application starts and release the gzipped output, when it completes. ff Yii's application component has two events that will come in handy in this case: CApplication::onBeginRequest and CApplication::onEndRequest. Let's use them. Insert the following code snippet in the index.php file after configuring an application but before running it: … require_once($yii); $app = Yii::createWebApplication($config); Chapter 1 13 // attaching a handler to application start Yii::app()->onBeginRequest = function($event) { // starting output buffering with gzip handler return ob_start("ob_gzhandler"); }; // attaching a handler to application end Yii::app()->onEndRequest = function($event) { // releasing output buffer return ob_end_flush(); }; $app->run(); There are many handy events defined inside Yii's core classes. You can get them all by searching for the function on text in the framework folder using your favorite IDE. Now, let's look at another example. In Yii, you can translate strings to different languages using Yii::t. As we all love perfect projects, all language translations should be up to date. If they are not, we would like to receive an e-mail about it. Events come in handy again here. In particular, the CMessageSource::onMissingTransl ation event that is called when the translation for a string passed to Yii::t is missing. This time we will use the application's configuration file protected/config/main.php to attach an event handler as follows: … 'components' => array( … // messages component class is CPhpMessageSource by default 'messages' => array( // using static class method as event handler 'onMissingTranslation' => array('MyEventHandler', 'handleMissingTranslation'), ), … ) … Under the Hood 14 Now, we should implement our handler. Create protected/components/ MyEventHandler.php as follows: class MyEventHandler { static function handleMissingTranslation($event) { // event class for this event is CMissingTranslationEvent // so we can get some info about the message $text = implode("\n", array( 'Language: '.$event->language, 'Category:'.$event->category, 'Message:'.$event->message )); // sending email mail('admin@example.com', 'Missing translation', $text); } } Let's look at another example. Let's assume we have a blog application and we need to send an e-mail when there is a new comment (Comment) to the blog post (Post). Comment is a standard AR model generated with Gii. Post is the same Gii-generated model except for some customized methods. We will need a custom event, NewCommentEvent, to store both Post and Comment models and a handler class, Notifier, that will do the work. 1. Let's start with protected/components/NewCommentEvent.php: class NewCommentEvent extends CModelEvent { public $comment; public $post; } It is pretty simple, we have just added two properties. 2. Now let's move on to protected/models/Post.php. All standard AR methods are omitted to emphasize what was added: class Post extends CActiveRecord { // custom method for adding a comment // to current post function addComment(Comment $comment){ $comment->post_id = $this->id; // creating event class instance $event = new NewCommentEvent($this); $event->post = $this; $event->comment = $comment; // triggering event Chapter 1 15 $this->onNewComment($event); return $event->isValid; } // defining onNewComment event public function onNewComment($event) { // Event is actually triggered here. This way we can use // onNewComment method instead of raiseEvent. $this->raiseEvent('onNewComment', $event); } } 3. Now it is time to implement the Notifier class. Create protected/components/ Notifier.php as follows: class Notifier { function comment($event){ $text = "There was new comment from {$event->comment->author} on post {$event->post- >title}"; mail('admin@example.com', 'New comment', $text); } } 4. Now it is time to get these together in protected/controllers/ PostController.php: class PostController extends CController { function actionAddComment() { $post = Post::model()->findByPk(10); $notifier = new Notifier(); // attaching event handler $post->onNewComment = array($notifier, 'comment'); // in the real application data should come from $_POST $comment = new Comment(); $comment->author = 'Sam Dark'; $comment->text = 'Yii events are amazing!'; // adding comment $post->addComment($comment); } } 5. After the comment has been added, admin will receive an e-mail about it. Under the Hood 16 There's more... It is not always necessary to attach an event handler. Let's look at how we can handle an event that is already declared inside an existing component by overriding a method of the base class. For example, we have a form model UserForm used to collect some information about our application user and we need to get the complete name from the first and the last name entered by the user. Fortunately, in CModel, which is the base class for all Yii models including form models, the CModel::afterValidate method is defined. This method is called after a successful form validation. Let's use it in our protected/models/UserForm.php model: class UserForm extends CFormModel { public $firstName; public $lastName; public $fullName; public function rules() { return array( // First name and last name are required array('firstName, lastName', 'required'), ); } function afterValidate() { // If this method was called then // the model is already filled // with data and data is valid // so we can use it safely: $this->fullName = $this->firstName.' '.$this->lastName; // It's important to call parent class method // so all other event handlers are called return parent::afterValidate(); } } We need to call the parent method inside of the afterValidate function because parent implementation calls onAfterValidate that actually raises events: protected function afterValidate() { $this->onAfterValidate(new CEvent($this)); } Chapter 1 17 An event's method name should always be defined as function eventHandler($event){…}, where $event is a CEvent instance. The CEvent class contains just two properties named sender and handled. The first property contains an object that calls the current event, while the second can be used to prevent calling all other, not yet executed handlers, by setting it to false. The approach described here can be used to customize your Active Record models and implement your own model behaviors. Further reading For further information, refer to the following URLs: ff http://www.yiiframework.com/doc/api/CComponent/ #raiseEvent-detail ff http://www.yiiframework.com/doc/api/ CComponent/#attachEventHandler-detail ff http://www.yiiframework.com/doc/api/ CComponent/#getEventHandlers-detail ff http://www.yiiframework.com/doc/api/ CComponent/#detachEventHandler-detail See also ff The Using getters and setters recipe ff The Configuring components recipe Using import and autoloading When programming with PHP, one of the most annoying things is loading additional code with include and require. Fortunately, you can do it automatically using the SPL class loader (http://php.net/manual/en/function.spl-autoload.php). Autoloading is one of the features that Yii relies on. Still, there are many questions about it on the forums. Let's get it clear and show how we can use it. When we use a class, for example, CDbCriteria, we are not including it explicitly so PHP initially cannot find it and tries to rely on the autoloading feature; the SPL autoloader, to be precise. In most cases, the Yii default autoloader (YiiBase::autoload) will be used. Under the Hood 18 For the sake of speed and simplicity, almost all core framework classes are loaded when needed without including or importing them explicitly. It's done through the YiiBase::$_coreClasses map, so loading core classes is very fast. Zii classes, such as CMenu, extension classes, or your own classes are not loaded automatically, so we need to import them first. To import classes, we will use Yii::import: ff import does not include a class immediately by default ff It does not include a class if it is not used ff It will not load a class twice, so it is safe to import the same class multiple times How to do it... 1. Let's assume that we have a custom class named LyricsFinder that finds lyrics for a given song. We have put it under protected/apis/lyrics/ and in our protected/controllers/TestController.php. We are trying to use it in the following way: class TestController extends CController { public function actionIndex($song) { $lyric = 'Nothing was found.'; $finder = new LyricsFinder(); if(!empty($song)) $lyric = $finder->getText($song); echo $lyric; } } 2. When executing it, we will get the following PHP error: include(LyricsFinder.php): failed to open stream: No such file or directory. 3. Yii helps us there a bit because at the error screen, we can see that the autoloader fails because it doesn't know where to look for our class. Therefore, let's modify our code: class TestController extends CController { public function actionIndex($song) { $lyric = 'Nothing was found.'; // importing a class Yii::import('application.apis.lyrics.LyricsFinder'); Chapter 1 19 $finder = new LyricsFinder(); if(!empty($song)) $lyric = $finder->getText($song); echo $lyric; } } Now our code works. The built-in Yii class loader requires that each class should be placed into a separate file named the same as the class itself. When developing using case insensitive filesystems such as ones used by Windows, make sure you're using the same case in both the filename and code since it can be a problem when you deploy your code to a case sensitive Linux server. How it works... Let's look at application.apis.lyrics.LyricsFinder. application is a standard alias that points to your application's protected folder and is translated into a filesystem path. The following table shows some more standard aliases: Alias Path application path_to_webroot/protected system path_to_webroot/framework zii path_to_webroot/framework/zii webroot path_to_webroot ext path_to_webroot/protected/extensions You can define your own aliases using the Yii::setPathOfAlias method. Typically, it can be done as the first lines of protected/config/main.php, so all other config parts will be able to use these new aliases. apis.lyrics are translated to apis/lyrics and are appended to a path retrieved from the application alias, and LyricsFinder is the class name we want to import. If LyricsFinder requires some additional classes located in its directory, then we can use Yii::import('application.apis.lyrics.*') to import the whole directory. Note that * does not include subfolders, so if you need lyrics/includes, you should add another import statement: Yii::import('application.apis.lyrics.includes.*'). Under the Hood 20 For performance reasons, it is better to use explicit paths with a class name instead of * if you are importing a single class. There's more... If you want your classes to be imported automatically like with Yii's core classes, then you can configure global imports in your main.php configuration file: return array( // … // global imports 'import'=>array( 'application.models.*', 'application.components.*', 'application.apis.lyrics.*', 'application.apis.lyrics.includes.*', 'application.apis.albums.AlbumFinder', ), Note that using *, with a huge amount of global imports could slow your application down as there will be too many directories to check. Downloading the example code To get the example code files for this book visit http://yiicookbook.org/code. Using exceptions Exceptions are a core PHP feature, but they are seldom used fairly. Yii makes exceptions very useful. There are two main areas where Yii exceptions come in handy, which are as follows: ff Exceptions allow the simplifying of the process of detecting and fixing application errors and special situations, such as database connection failure or API failure ff Exceptions allow the generating of different HTTP responses in a very clean way Generally, an exception should be thrown when a component cannot handle a special situation, such as the one said earlier, and needs to leave it to higher-level components. Chapter 1 21 How to do it… 1. Let's assume that we have an application/apis/lyrics/LyricsFinder.php class that makes an HTTP request to an API using CURL and returns lyrics for a song based on its name. This is how we can use exceptions inside of it: // create some custom exceptions to be able to catch them // specifically if needed // general lyrics finder exception class LyricsFinderException extends CException {} // used when there is a connection problem class LyricsFinderHTTPException extends LyricsFinderException{} class LyricsFinder { private $apiUrl = 'http://example.com/lyricsapi&songtitle=%s'; function getText($songTitle) { $url = $this->getRequestUrl($songTitle); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($curl); // if there is an HTTP error, we'll throw an // exception if($result===false) { $errorText = curl_error($curl); curl_close($url); throw new LyricsFinderHTTPException($errorText); } curl_close($curl); return $result; } private function getRequestUrl($songTitle) { return sprintf($this->apiUrl, urlencode($songTitle)); } } Under the Hood 22 2. As we don't know how a specific application needs to handle its API connection, we will leave it to the application itself by throwing a custom exception LyricsFinderHTTPException. This is how we can handle it in our protected/ controllers/TestController.php class: class TestController extends CController { public function actionIndex($song) { $lyric = 'Nothing was found.'; // importing api class Yii::import('application.apis.lyrics.LyricsFinder'); $finder = new LyricsFinder(); if(!empty($song)) { // We don't want to show user an error. // Instead we want to apologize and // invite him to try again later. try { $lyric = $finder->getText($song); } // we are looking for specific exception here catch (LyricsFinderHTTPException $e) { echo 'Sorry, we cannot process your request. Try again later.'; } } echo $lyric; } } 3. Another usage of Yii exceptions is the generation of different HTTP responses by throwing CHttpException. For example, an action that displays a blog post represented by a Post model loaded by its ID will look like this: class PostController extends CController { function actionView() { if(!isset($_GET['id'])) // If there is no post ID supplied, request is Chapter 1 23 // definitely wrong. // According to HTTP specification its code is 400. throw new CHttpException(400); // Finding a post by its ID $post = Post::model()->findByPk($_GET['id']); if(!$post) // If there is no post with ID specified we'll // generate HTTP response with code 404 Not Found. throw new CHttpException(404); // If everything is OK, render a post $this->render('post', array('model' => $post)); } } How it works… Yii converts all non-fatal application errors to CException automatically. Additionally, the default exception handler raises either the onError event or the onException event. The default event handler writes a log message with the error level set to error. Additionally, if your application's YII_DEBUG constant is set to true, unhandled exceptions or errors will be displayed at a handy error screen. This screen includes a call stack trace, a code area where the exception was raised, and the file and line where you can look for the code to fix. There's more... For further information, refer to the following URLs: ff http://php.net/manual/en/language.exceptions.php ff http://www.yiiframework.com/doc/api/CException/ ff http://www.yiiframework.com/doc/api/CHttpException/ Under the Hood 24 Configuring components Yii is a very customizable framework. Moreover, as in every customizable code, there should be a convenient way to set up different application parts. So in Yii, this is provided through a configuration file named main.php located at protected/config/. How to do it… If you have worked with Yii before, then you have probably configured a database connection: return array( … 'components'=>array( 'db'=>array( 'class'=>'system.db.CDbConnection', 'connectionString'=>'mysql:host=localhost; dbname=database_name', 'username'=>'root', 'password'=>'', 'charset'=>'utf8', ), … ), … ); This way of configuring a component is used when you want to use a component across all application parts. With the preceding configuration, you can access a component by its name, such as Yii::app()->db. How it works… When you are using the Yii::app()->db component for the first time directly or through the Active Record model, Yii creates a component and initializes its public properties with the corresponding values provided in the db array under the components section of the main. php application configuration file. In the preceding code, the 'connectionString' value will be assigned to CDbConnection::connectionString, the 'username' value will be assigned to CDbConnection::username, and so on. If you want to find out what 'charset' stands for or want to know what else you can configure in the db component, then you need to know its class. In case of the db component, the class is CDbConnection. You can refer to its API page at http://www.yiiframework.com/ doc/api/CDbConnection/ and look for its public properties that you can set from config. Chapter 1 25 In the preceding code, the 'class' property is a bit special because it is used to specify the component's class name. It does not exist in the CDbConnection class. Therefore, it can be used to override a class as follows: return array( … 'components'=>array( 'db'=>array( 'class'=>'application.components.MyDbConnection', … ), … ), … ); This way, you can override each application's component and it is very useful whenever a standard component does not fit your application. There's more... Now, let's find out which standard Yii application components you can configure. There are two application types bundled with Yii which are as follows: ff Web application (CWebApplication) ff Console application (CConsoleApplication) Both are extended from CApplication, so both console and web applications are sharing its components. You can get the component names from API pages (http://www.yiiframework.com/ doc/api/) and the source code of the registerCoreComponents application method, but let's list them here so that it can be used as a reference. Both console and web application components are listed in the following table: Component name Default/suggested component class Description coreMessages CPhpMessageSource This component provides the source for translating Yii framework messages. db CDbConnection This component provides a database connection. messages CPhpMessageSource This component provides the source for translating application messages. errorHandler CErrorHandler This component handles PHP errors and uncaught exceptions. Under the Hood 26 Component name Default/suggested component class Description securityManager CSecurityManager This component provides security-related services, such as hashing, encryption, and so on. statePersister CStatePersister This component provides global state persistence methods. format CFormatter This component provides a set of commonly used data formatting methods. cache CFileCache This component provides a caching feature. Additional components available only for web application are listed in the following table: Component name Default component class Description session CHttpSession This component provides the session- related functionalities. request CHttpRequest This component encapsulates the $_SERVER variable and resolves its inconsistency among different web servers. It also manages the cookies sent from and to the user. urlManager CUrlManager URL router; used for both generating and resolving application URLs. assetManager CAssetManager This component manages the publishing of private asset files. user CWebUser This component represents the user's session information. themeManager CThemeManager This component manages themes. authManager CPhpAuthManager This component manages role-based access control (RBAC). clientScript CClientScript This component manages client scripts (JavaScript and CSS). widgetFactory CWidgetFactory This component creates widgets and supports widget skinning. You can add your own application components (classes extended from CComponent) by simply adding new configuration items and pointing their class properties to your custom classes. Chapter 1 27 Configuring widget defaults In Yii, code pieces commonly used in views are placed into widgets. For example, a widget can render a tag cloud or provide a custom form input type. Core widgets are highly configurable and are used in views as follows: widget('CLinkPager', array( 'pages' => $pages, 'pageSize' => 15, ))?> In the preceding code, we are using $this->widget that calls a CLinkPager widget with an array of parameters to display a pagination. pages and pageSize are both assigned to the corresponding public properties of the CLinkPager widget before it is rendered. Note that we have changed the count of items per page to 15 in our example. If we want our pagination to display 15 items per page on all pages of our application, then we will need to provide a pageSize parameter with a value of 15 for all CLinkPager widget calls. Is there a better way? Definitely, yes. How to do it… A Yii web application provides a bunch of components. One of them is a widget factory that since Yii 1.1.3 can be used to set widget defaults. 1. Let's use it to set pageSize application-wide. We will need to edit the main.php application configuration file as follows: return array( … 'components'=>array( 'widgetFactory'=>array( 'widgets'=>array( 'CLinkPager'=>array( 'pageSize'=>15, ), … ), ), … ), ); 2. Now, the default value for pageSize of CLinkPager will be 15, so if we omit this parameter for all the CLinkPager classes of the application then it will be 15, application-wide. Under the Hood 28 3. Moreover, we still can override the pageSize value for a specific widget: widget('CLinkPager', array( 'pages' => $pages, 'pageSize' => 5, ))?> This works much like the CSS cascade. You set the default overall style in an external file, but can still override this through inline styles for individual widgets. See also ff The Configuring components recipe Using Yii core collections Yii has a set of collection classes used mainly for internal purposes which are not described in the definitive guide, but are still very useful for applications: ff Lists: CList, CTypedList ff Maps: CMap, CAttributeCollection ff Queue: CQueue ff Stack: CStack How to do it… All collections implement SPL IteratorAggregate, Traversable, and Countable. Lists and maps also implement SPL ArrayAccess. It allows the use of collections like a standard PHP construct. The following is a snippet from the CList API: ff The following is the code snippet from the CList API: // append at the end $list[]=$item; // $index must be between 0 and $list->Count $list[$index]=$item; // remove the item at $index unset($list[$index]); // if the list has an item at $index if(isset($list[$index])) Chapter 1 29 // traverse each item in the list foreach($list as $index=>$item) // returns the number of items in the list $n=count($list); ff CList is an integer-indexed collection. Compared to the native PHP array, it adds stricter checks, can be used in OO fashion, and allows to make a collection read-only: $list = new CList(); $list->add('python'); $list->add('php'); $list->add('java') if($list->contains('php')) $list->remove('java'); $anotherList = new CList(array('python', 'ruby')); $list->mergeWith($anotherList); $list->setReadOnly(true); print_r($list->toArray()); ff There is another list collection named CTypedList that ensures that the list contains only items of a certain type: $typedList = new CTypedList('Post'); $typedList->add(new Post()); $typedList->add(new Comment()); As we are trying to add a comment to the Post list, the preceding code will give you the following exception: CTypedList can only hold objects of Post class. ff CMap allows using every value, integer or not, as a key. Just like in CList, it can also be used in the native PHP style, has almost the same set of OO methods, and allows making a collection read-only: $map = new CMap(); $map->add('php', array('facebook', 'wikipedia', 'wordpress', 'drupal')); $map->add('ruby', array('basecamp', 'twitter')); print_r($map->getKeys()); Under the Hood 30 ff There is also one handy static method named CMap::mergeArray that can be used to recursively merge two associative arrays while replacing scalar values: $apps1 = array( 'apps' => array( 'task tracking', 'bug tracking', ), 'is_new' => false ); $apps2 = array( 'apps' => array( 'blog', 'task tracking', ), 'todo' => array( 'buy milk', ), 'is_new' => true ); $apps = CMap::mergeArray($apps1, $apps2); CVarDumper::dump($apps, 10, true); The result of the preceding code is as follows: array ( 'apps' => array ( '0' => 'task tracking' '1' => 'bug tracking' '2' => 'blog' '3' => 'task tracking' ) 'is_new' => true 'todo' => array ( '0' => 'buy milk' ) ) Chapter 1 31 ff CAttributeCollection includes all of the CMap functionality and can work with data just like properties: $col = new CAttributeCollection(); // $col->add('name','Alexander'); $col->name='Alexander'; // echo $col->itemAt('name'); echo $col->name; ff CQueue and CStack implements a queue and a stack respectively. A queue works as FIFO: first in, first out, and the stack is LIFO: last in, first out. In the same way as list and map collections, these can be used in native PHP style and have OO style methods: $queue = new CQueue(); // add some tasks $queue->enqueue(new Task('buy milk')); $queue->enqueue(new Task('feed a cat')); $queue->enqueue(new Task('write yii cookbook')); // complete a task (remove from queue and return it) echo 'Done with '.$queue->dequeue(); echo count($queue).' items left.'; // return next item without removing it echo 'Next one is '.$queue->peek(); foreach($queue as $task) print_r($task); $garage = new CStack(); // getting some cars into the garage $garage->push(new Car('Ferrari')); $garage->push(new Car('Porsche')); $garage->push(new Car('Kamaz')); // Ferrari and Porsche can't get out // since there is… echo $garage->peek(); // Kamaz! // we need to get Kamaz out first $garage->pop(); $porsche = $garage->pop(); $porsche->drive(); Under the Hood 32 Working with requests You can work with request data directly using PHP superglobals such as $_SERVER, $_GET, or $_POST but the better way is to use Yii's powerful CHttpRequest class that resolves inconsistencies among different web servers, manages cookies, provides some additional security, and has a nice set of OO methods. How to do it… You can access the request component in your web application by using Yii::app()->getRequest(). So, let's review the most useful methods and their usage, methods that return different parts of the current URL. In the following table, the returned parts are marked with a bold font. Methods Results getUrl http://cookbook.local/test/index?var=val getHostInfo http://cookbook.local/test/index?var=val getPathInfo http://cookbook.local/test/index?var=val getRequestUri http://cookbook.local/test/index?var=val getQueryString http://cookbook.local/test/index?var=val The methods that allow us to ensure the request types are getIsPostRequest, getIsAjaxRequest, and getRequestType. ff For example, we can use getIsAjaxRequest to serve different content based on the request type: class TestController extends CController { public function actionIndex() { if(Yii::app()->request->isAjaxRequest) $this->renderPartial('test'); else $this->render('test'); } } In the preceding code, we are rendering a view without a layout if the request is made through AJAX. Chapter 1 33 ff While PHP provides superglobals for both POST and GET, Yii allows us to omit some additional checks: class TestController extends CController { public function actionIndex() { $request = Yii::app()->request; $param = $request->getParam('id', 1); // equals to $param = isset($_REQUEST['id']) ? $_REQUEST['id'] : 1; $param = $request->getQuery('id'); // equals to $param = isset($_GET['id']) ? $_GET['id'] : null; $param = $request->getPost('id', 1); // equals to $param = isset($_POST['id']) ? $_POST['id'] : 1; } } ff getPreferredLanguage tries to determine the user's preferred language. It can't be completely accurate, but it is good to use it as a fallback in case the user has not specified a preferred language manually. class TestController extends CController { public function actionIndex() { $request = Yii::app()->request; $lang = $request->preferredLanguage; // Trying to get language setting from // Settings table that holds id of the user, // setting name and setting value. $criteria = new CDbCriteria(); $criteria->compare('user_id', $request- >getQuery('userid')); $criteria->compare('key', 'language'); $setting = Settings::model()->find($criteria); if($setting) $lang = $setting->value; Under the Hood 34 Yii::app()->setLanguage($lang); echo Yii::t('app', 'Language is: ').$lang; } } ff sendFile allows us to initiate a file download as follows: class TestController extends CController { public function actionIndex() { $request = Yii::app()->getRequest(); $request->sendFile('test.txt', 'File content goes here.'); } } This action will trigger a file download and send all necessary headers, including content type (mimetype) and content length. The MIME type, if not set manually as a third parameter, will be guessed based on the filename's extension. ff The last thing we are going to show in this chapter is the getCookies method. It returns a CCookieCollection class instance that allows us to work with cookies. As CCookieCollection extends CMap, we can use some native PHP methods as follows: class TestController extends CController { public function actionIndex() { $request = Yii::app()->request; // getting a cookie $cookie = $request->cookies['test']; if($cookie) // printing cookie value echo $cookie->value; else { // creating new cookie $cookie=new CHttpCookie('test','I am a cookie!'); $request->cookies['test'] = $cookie; } } Chapter 1 35 There's more... If you are working with a lot of cookie values and want to shorten the code provided, then you can use a helper as follows: class Cookie { public static function get($name) { $cookie=Yii::app()->request->cookies[$name]; if(!$cookie) return null; return $cookie->value; } public static function set($name, $value, $expiration=0) { $cookie=new CHttpCookie($name,$value); $cookie->expire = $expiration; Yii::app()->request->cookies[$name]=$cookie; } } After you drop this code into protected/components/Cookie.php, you will be able to perform the following: class TestController extends CController { public function actionIndex() { $cookie = Cookie::get('test'); if($cookie) echo $cookie; else Cookie::set('test','I am a cookie!!'); } } 2 Router, Controller, and Views In this chapter, we will cover: ff Configuring URL rules ff Generating URLs by path ff Using regular expressions in URL rules ff Creating URL rules for static pages ff Providing your own URL rules at runtime ff Using a base controller ff Using external actions ff Displaying static pages with CViewAction ff Using flash messages ff Using the controller context in a view ff Reusing views with partials ff Using clips ff Using decorators ff Defining multiple layouts ff Paginating and sorting data Router, Controller, and Views 38 Introduction This chapter will help you to learn some handy things about the Yii URL router, controllers, and views. You will be able to make your controllers and views more flexible. Configuring URL rules The Yii URL router is quite powerful and does two main tasks: it resolves URLs into internal routes and creates URLs from these routes. Router rules description is scattered over the official Yii guide and API docs. Let's try to understand how to configure application rules by example. Getting ready 1. Create a fresh Yii application using yiic webapp as described in the official guide (http://www.yiiframework.com/doc/guide/) and find your protected/ config/main.php file. It should contain the following: // application components 'components'=>array( … // uncomment the following to enable URLs in path-format 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( '/'=>'/view', '//'=>'/', '/'=>'/', ), ), 2. Delete everything from rules as we are going to start from scratch. 3. In your protected/controllers directory, create WebsiteController.php with the following code inside it: class WebsiteController extends CController { public function actionIndex() { echo "index"; } public function actionPage($alias) { Chapter 2 39 echo "Page is $alias."; } } This is the application controller that we are going to customize URLs for. 4. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder: Options +FollowSymLinks IndexIgnore */* RewriteEngine on # if a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # otherwise forward it to index.php RewriteRule . index.php How to do it... Our website should display the index page at /home and all other pages at /page/. Additionally, /about should lead to a page with the alias about. 1. Add the following to your rules in protected/config/main.php: 'home' => 'website/index', '' => 'website/page', 'page/' => 'website/page', 2. After saving your changes, you should be able to browse the following URLs: ‰‰ /home ‰‰ /about ‰‰ /page/about ‰‰ /page/test The following screenshot shows part of a page that opens when the /about URL is used: Router, Controller, and Views 40 How it works... Let's review what was done and why it works. We'll start with the rightmost part of the first rule: 'home' => 'website/index', What is website/index exactly? In the Yii application, each controller and its actions have corresponding internal routes. A format for an internal route is moduleID/controllerID/ actionID. For example, the actionPage method of WebsiteController corresponds to the website/page route. So, in order to get the controller ID, you should take its name without the Controller postfix and make its first letter lowercase. To get an action ID, you should take the action method name without the action prefix and, again, make its first letter lowercase. Now, what is home? To understand it in a better way, we need to know, at least superficially, what's happening when we access our application using different URLs. When we use /home, the URL router checks our rules one by one starting from the top, trying to match the URL entered with the rule. If the match is found, then the router gets the controller and its action from an internal route assigned to the rule and is executing it. So, / home is the URL pattern that defines which URLs will be processed by the rule it belongs to. The fewer rules you have, the fewer checks are needed if the URLs do not match. Fewer URLs means more performance. There's more... You can also create parameterized rules using a special syntax. Let's review the third rule: 'page/' => 'website/page', Here, we are defining an alias parameter that should be specified in the URL after /page/. It can be virtually anything and it will be passed as the $alias parameter to WebsiteContr oller::actionPage($alias). You can define a pattern for such a parameter. We did it for the second rule: '' => 'website/page', The alias here should match about, otherwise, the rule will not be applied. Chapter 2 41 Further reading For further information, refer to the following URLs: ff http://www.yiiframework.com/doc/guide/1.1/en/basics.controller ff http://www.yiiframework.com/doc/guide/en/topics.url ff http://www.yiiframework.com/doc/api/1.1/CUrlManager ff http://www.yiiframework.com/doc/api/1.1/CUrlRule See also ff The Using regular expressions in URL rules recipe ff The Creating URL rules for static pages recipe ff The Providing your own URL rules at runtime recipe Generating URLs by path Yii allows you to not only route your URLs to different controller actions but also to generate a URL by specifying a proper internal route and its parameters. This is really useful because you can focus on internal routes while developing your application, and only worry about real URLs before going live. Never specify URLs directly and make sure you use the Yii URL toolset. It will allow you to change URLs without rewriting a lot of application code. Getting ready 1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php file. Replace the rules array as follows: // application components 'components'=>array( … // uncomment the following to enable URLs in path-format /* 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( '' => 'website/page', 'page/about/' => 'website/page', 'page/' => 'website/page', ), Router, Controller, and Views 42 2. In your protected/controllers directory, create WebsiteController with the following code inside: class WebsiteController extends CController { public function actionIndex() { echo "index"; } public function actionPage($alias) { echo "Page is $alias."; } } This is our application controller that we are going to generate custom URLs for. 3. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder: Options +FollowSymLinks IndexIgnore */* RewriteEngine on # if a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # otherwise forward it to index.php RewriteRule . index.php How to do it... We need to generate URLs pointing to the index and page actions of WebsiteController. Depending on where we need it, there are different ways for doing it, but the basics are the same. Let's list some methods that generate URLs. CHtml::link() and some other CHtml methods, such as form, refresh, and ajaxLink, all accept URLs and are typically used in views. These use CHtml::normalizeUrl internally to resolve internal routes. Therefore, you should pass data in one of the following formats: ff URL string: In this case, the URL passed will be used as is ff array(internal route, param => value, param => value, …): In this case, a URL will be generated Chapter 2 43 What is internal route? Each controller and its actions have corresponding routes. A format for a route is moduleID/controllerID/actionID. For example, the actionPage method of WebsiteController corresponds to the website/page route. To get a controller ID, you should take its name without the Controller postfix and make its first letter lowercase. To get an action ID, you should take the action method name without the action prefix and, again, make its first letter lowercase. Parameters are the $_GET variables that will be passed to an action with an internal route specified. For example, if we want to create a URL to WebsiteController::actionIndex that passes the $_GET['name'] parameter to it, it can be done like this: echo CHtml::link('Click me!', array('website/index', 'name' => 'Qiang')); URLs are also helpful when using the controller. Inside the controller, you can use createUrl and createAbsoluteUrl to get both relative and absolute URLs: class WebsiteController extends CController { public function actionTest() { echo $this->createUrl('website/page', array('alias' => 'about')); echo '
'; echo $this->createAbsoluteUrl('website/page', array('alias' => 'test')); } // the rest of the methods } As we have URL rules defined in the router configuration, we will get the following URLs: ff /about ff http://example.com/page/test Relative URLs can be used inside your application while absolute ones should be used for pointing to locations outside of your website (like other websites) or for linking to resources meant to be accessed from outside (RSS feeds, e-mails, and so on). Router, Controller, and Views 44 When you cannot get a controller instance, for example, when you implement a console application, you can use the application's methods: echo Yii::app()->createUrl('website/page', 'alias' => 'about'); echo Yii::app()->createAbsoluteUrl('website/page', 'alias' => 'test'); The difference is that when using controller-specific methods, you can omit both controller and module names. In this case, the current module name and the current controller name are used: class MyController extends CController { public function actionIndex() { // As we're inside of controller, createUrl will assume that // URL is for current controller echo $this->createUrl('index'); } } How it works... All URL building tools we have reviewed internally use the CWebApplication:: createUrl method that calls CUrlManager::createUrl. It tries to apply routing rules one by one, starting from the top. The first matched rule is applied. If no rules are matched, then the default URL form is generated. There's more... For further information, refer to the following URLs: ff http://www.yiiframework.com/doc/guide/en/basics.controller ff http://www.yiiframework.com/doc/guide/en/topics.url ff http://www.yiiframework.com/doc/api/CUrlManager ff http://www.yiiframework.com/doc/api/CHtml/#normalizeUrl-detail ff http://www.yiiframework.com/doc/api/CHtml/#link-detail ff http://www.yiiframework.com/doc/api/CController/#createUrl- detail ff http://www.yiiframework.com/doc/api/CWebApplication/#createUrl- detail Chapter 2 45 See also ff The Configuring URL rules recipe ff The Creating URL rules for static pages recipe ff The Providing your own URL rules at runtime recipe Using regular expressions in URL rules One of the hidden features of the Yii URL router is that you can use regular expressions that are pretty powerful when it comes to strings handling. Getting ready 1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php file. It should contain the following: // application components 'components'=>array( … // uncomment the following to enable URLs in path-format /* 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( '/'=>'/view', '//'=>'/', '/'=>'/', ), ), 2. Delete everything from rules as we are going to start from scratch. 3. In your protected/controllers directory, create PostController.php with the following code inside it: class PostController extends CController { public function actionView($alias) { echo "Showing post with alias $alias."; } Router, Controller, and Views 46 public function actionIndex($order = 'DESC') { echo "Showing posts ordered $order."; } public function actionHello($name) { echo "Hello, $name!"; } } This is our application controller that we are going to access using our custom URLs. 4. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder: Options +FollowSymLinks IndexIgnore */* RewriteEngine on # if a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # otherwise forward it to index.php RewriteRule . index.php How to do it... We want our PostController action to accept parameters according to some specified rules and give the 404 not found HTTP response for all parameters that do not match. In addition, post/index should have an alias URL, archive. Let's use regular expressions in the configuration file to achieve this: 'post/' => 'post/view', '(posts|archive)' => 'post/index', '(posts|archive)/' => 'post/index', 'sayhello/' => 'post/hello', The following URLs will be successful: ff http://example.com/post/test-post ff http://example.com/posts ff http://example.com/archive ff http://example.com/posts/ASC ff http://example.com/sayhello/Александр Chapter 2 47 The following URLs will fail: ff http://example.com/archive/test ff http://example.com/post/another_post The following screenshot shows that the URL http://example.com/post/test-post has run successfully: The following screenshot shows that the URL http://example.com/archive/test did not run successfully and encountered an error: Router, Controller, and Views 48 How it works... You can use regular expressions in both parameter definition and the rest of the rule. Let's read our rules one by one. 'post/' => 'post/view', The alias parameter should contain one or more English letters or a dash. No other symbols are allowed. '(posts|archive)' => 'post/index', Both posts and archive lead to post/index. '(posts|archive)/' => 'post/index', Both posts and archive lead to post/index. The order parameter can only accept two values: DESC and ASC. 'sayhello/' => 'post/hello', You should specify the name part but there are no restrictions on what characters are allowed. Note that regardless of the rule used, the developer should never assume that input data is safe. There's more... To learn more about regular expressions, you can use the following sources: ff http://www.php.net/manual/en/reference.pcre.pattern.syntax.php ff Mastering Regular Expressions, Jeffrey Friedl available at http://regex.info/ See also ff The Configuring URL rules recipe Creating URL rules for static pages A website typically contains some static pages. Usually, they include /about, /contact, / tos, and so on, and it is common to handle these pages in a single controller action. Let's find a way to create URL rules for these types of pages. Chapter 2 49 Getting ready 1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php file. It should contain the following: // application components 'components'=>array( … // uncomment the following to enable URLs in path-format /* 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( '/'=>'/view', '//'=>'/', '/'=>'/', ), ), 2. Delete everything from rules as we are going to start from scratch. 3. In your protected/controllers directory, create WebsiteController.php with the following code: class WebsiteController extends CController { public function actionPage($alias) { echo "Page is $alias."; } } 4. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, you should add the following lines to the .htaccess file under your webroot folder: Options +FollowSymLinks IndexIgnore */* RewriteEngine on # if a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # otherwise forward it to index.php RewriteRule . index.php Router, Controller, and Views 50 How to do it... The most straightforward way is to define a rule for each page, as follows: '' => 'website/page', '' => 'website/page', '' => 'website/page', Using regular expressions, we can compact it to a single rule: '' => 'website/page', Now, what if we want the URL to be /tos and an alias parameter to be terms_of_service? No problem, we can use default parameters to achieve it: 'tos' => array('website/page', 'defaultParams' => array('alias' => 'terms_of_service')), OK. What if we have many pages and want to be able to dynamically create pages without adding more rules or changing existing ones? We can achieve this with the following rule: '' => 'website/page' As this rule matches everything, we need to place it last, so it won't affect all other rules. In addition, default rules with one slug, such as controller name, will stop working. To overcome this issue, we need to add default rules, which we deleted in the Getting ready section of this recipe. How it works... Let's read the rules we just wrote. '' => 'website/page', If the URL is /about, then pass it as the alias parameter to website/page. '' => 'website/page', If the URL is /about or /contact or /tos, then pass it as the alias parameter to website/page. 'tos' => array('website/page', 'defaultParams' => array('alias' => 'terms_of_service')), When the URL is /tos, pass terms_of_service as the alias parameter value. Chapter 2 51 This rule is a bit special because it uses the default parameter option. The default parameter allows you to set a value that will be used if a parameter with a name specified is omitted. When you need to specify an option for the rule, you should use an array notation: 'pattern' => array('internal/route', 'option' => 'value', 'option' => 'value', …), For a list of options you can set, refer to the following API page: http://www.yiiframework.com/doc/api/1.1/CUrlRule As the last step we've re-added default rules: '/'=>'/view', '//'=>'/', '/'=>'/', These are matching common URLs such as /user/123, /user/edit/123, or /user/add. The reason for adding these again is the fact that the rule matches everything. That means the URL manager will always stop at this rule and will never try to apply a default convention. In order to get it back we should put default rules before . See also ff The Configuring URL rules recipe ff The Using regular expressions in URL rules recipe Providing your own URL rules at runtime When you are developing an application with a pluggable module architecture, you most likely need to somehow inject your module-specific rules into an existing application. Getting ready 1. Set up a new application using yiic webapp. 2. Add .htaccess, shown in the official URL management guide to your webroot folder. 3. Uncomment the urlManager section in protected/config/main.php and add 'showScriptName' => false. 4. Generate the page module using Gii. You need to uncomment the gii section under modules and set a password. Then, open http://localhost/gii in your browser. 5. Don't forget to add your new module to the modules list in your application configuration. Router, Controller, and Views 52 The Yii code generator is shown in the following screenshot: How to do it... 1. Create ModuleUrlManager.php in your protected/components directory with the following code inside it: modules)) { foreach(Yii::app()->modules as $moduleName => $config) { $module = Yii::app()->getModule($moduleName); if(!empty($module->urlRules)) { Yii::app()->getUrlManager()->addRules ($module->urlRules); } } } return true; } } Chapter 2 53 2. In your application configuration file, add the following line: 'onBeginRequest' => array('ModuleUrlManager', 'collectRules'), 3. Now, in your page module, you can add custom rules. To do so, open PageModule. php and add: public $urlRules = array( 'test' => 'page/default/index', ); 4. To test if it works, open your browser and go to http://example.com/test. This page should look like the one shown in the following screenshot: This is the view content for action "index". The action belongs to the controller "DefaultController" in the "page" module. 5. You can still override URL rules from your main application configuration file. So, what you specify in module's urlRules is used only when the main application rules don't match. How it works... Let's review the ModuleUrlManager::collectRules method. If there are modules defined in our application, then we are checking if the urlRules public property exists. If it does, then there are some rules defined in the module and they are added using CUrlManager::addRules. The CUrlManager::addRules description says, "In order to make the new rules effective, this method must be called before CWebApplication::processRequest." Router, Controller, and Views 54 Now, let's check how our application works. In our index.php file, we have the following line: Yii::createWebApplication($config)->run(); After being initialized with the configuration, we call CWebApplication::run(): public function run() { if($this->hasEventHandler('onBeginRequest')) $this->onBeginRequest(new CEvent($this)); $this->processRequest(); if($this->hasEventHandler('onEndRequest')) $this->onEndRequest(new CEvent($this)); } As we can see, there is an onBeginRequest event raised just before calling processRequest. That is why we are attaching our class method to it. There's more... As instantiating all application modules on every request is not good for performance, it is good to cache module rules. Caching strategy can vary depending on your application. Let's implement a simple one: modules)) { $cache = Yii::app()->getCache(); foreach(Yii::app()->modules as $moduleName => $config) { $urlRules = false; if($cache) $urlRules = $cache->get('module.urls.'.$moduleName); if($urlRules===false){ $urlRules = array(); $module = Yii::app()->getModule($moduleName); if(isset($module->urlRules)) $urlRules = $module->urlRules; Chapter 2 55 if($cache) $cache->set('module.urls.'.$moduleName, $urlRules); } if(!empty($urlRules)) Yii::app()->getUrlManager()->addRules($urlRules); } } return true; } } This implementation caches URL rules per module. So, adding new modules is not a problem but changing existing ones requires you to flush the cache manually using Yii::app()- >cache->flush(). In order to see it in action, you need to define the cache under the components section of your protected/config/main.php file, as follows: 'cache' => array( 'class' => 'CFileCache', ), See also ff The Configuring URL rules recipe Using a base controller In many frameworks, the concept of a base controller that is being extended by other ones is described right in the guide. In Yii, it is not in the guide as you can achieve flexibility in many other ways. Still, using a base controller is possible and can be useful. Getting ready We are going to set up a new application using yiic webapp. Let's say we want to add some controllers that will be accessible only when the user is logged in. We can surely set this constraint for each controller separately, but we will do it in a better way. Router, Controller, and Views 56 How to do it... 1. First, we will need a base controller that our user-only controllers will use. Let's create SecureController.php in the protected/components directory with the following code: array('@'), ), array('deny', 'users'=>array('*'), ), ); } } 2. Now, go to the Gii controller generator and enter SecureController into the Base Class field. You will get something like this: class TestController extends SecureController { public function actionIndex() { $this->render('index'); } … } 3. Now, your TestController index will be only accessible if the user is logged in, even though we have not declared it explicitly in the TestController class. You can check it by visiting /index.php?r=test/index while not being logged in. Chapter 2 57 How it works... The trick is nothing more than a basic class inheritance. If filters or accessRules is not found in TestController, then it will be called from SecureController. Using external actions In Yii, you can define controller actions as separate classes and then connect them to your controllers. This way, you can reuse some common functionality. For example, you can move the backend for autocomplete fields to an action and save some time by not having to write it over and over again. Another simple example that we will review is deleting a model. Getting ready 1. Set up a new application using yiic webapp. 2. Create a database schema with the following script: CREATE TABLE `post` ( `id` int(10) unsigned NOT NULL auto_increment, `created_on` int(11) unsigned NOT NULL, `title` varchar(255) NOT NULL, `content` text NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL auto_increment, `username` varchar(200) NOT NULL, `password` char(40) NOT NULL, PRIMARY KEY (`id`) ); 3. Generate the Post and User models using Gii. Add some data to the tables. Router, Controller, and Views 58 How to do it... 1. Let's write protected/controllers/PostController.php. It is a usual delete action for posts: class PostController extends CController { function actionIndex() { $posts = Post::model()->findAll(); $this->render('index', array( 'posts' => $posts, )); } function actionDelete($id) { $post = Post::model()->findByPk($id); if(!$post) throw new CHttpException(404); if($post->delete()) $this->redirect('post/index'); throw new CHttpException(500); } } We have defined two actions. One lists all posts and another deletes a specified post if it exists and redirects back to the index action. You can try these using /index. php?r=post/index and /index.php?r=post/delete&id=1. 2. Now, let's do the same in a separate action class. Create DeleteAction.php in your protected/components directory as follows: class DeleteAction extends CAction { function run() { if(empty($_GET['id'])) throw new CHttpException(404); $post = Post::model()->findByPk($_GET['id']); if(!$post) throw new CHttpException(404); Chapter 2 59 if($post->delete()) $this->redirect('post/index'); throw new CHttpException(500); } } 3. Let's use it inside our controller. Delete actionDelete; we will not need it anymore. Then, add the actions method: class PostController extends CController { function actions() { return array( 'delete' => 'DeleteAction', ); } … } 4. Try /index.php?r=post/delete&id=2 again. It should work exactly the same way as it did last time. Now, we are using the external delete action for PostController, but what about UserController? To use DeleteAction with UserController we need to customize it first. We do this as follows: class DeleteAction extends CAction { public $pk = 'id'; public $redirectTo = 'index'; public $modelClass; function run() { if(empty($_GET[$this->pk])) throw new CHttpException(404); $model = CActiveRecord::model($this->modelClass) ->findByPk($_GET[$this->pk]); if(!$model) throw new CHttpException(404); Router, Controller, and Views 60 if($model->delete()) $this->redirect($this->redirectTo); throw new CHttpException(500); } } 5. Now, we can use this action for both PostController and UserController. For PostController, we do this as follows: class PostController extends CController { function actions() { return array( 'delete' => array( 'class' => 'DeleteAction', 'modelClass' => 'Post', ); ); } … } 6. For UserController, we do this as follows: class UserController extends CController { function actions() { return array( 'delete' => array( 'class' => 'DeleteAction', 'modelClass' => 'User', ), ); } … } 7. Try using /index.php?r=post/delete&id=3 and /index.php?r=user/ delete&id=1. This way, you can save yourself a lot of time by implementing and reusing external actions for tasks of a similar type. Chapter 2 61 How it works... Every controller can be built from external actions like a puzzle from pieces. The difference is that you can make external actions very flexible and reuse them in many places. In the final version of DeleteAction, we defined some public properties. As DeleteAction is a component, we can set its properties through the configuration file. In our case, we pass the configuration into the action's controller method, used to add actions to a module. There's more… For further information, refer to the following URLs: ff http://www.yiiframework.com/doc/api/CAction/ ff http://www.yiiframework.com/doc/api/CController#actions-detail Displaying static pages with CViewAction If you have a few static pages and aren't going to change them very frequently, then it's not worth querying the database and implementing page management for them. Getting ready Set up a new application using yiic webapp. How to do it... 1. We just need to connect CViewAction to our controller. class SiteController extends CController { function actions() { return array( 'page'=>array( 'class'=>'CViewAction', ), ); } } 2. Now, put your pages into protected/views/site/pages, and name them about.php and contact.php. Router, Controller, and Views 62 3. Now, you can try your pages by typing in the URL http://example.com/index. php?r=site/page&view=contact. 4. Alternatively, you can type in the URL http://example.com/site/page/view/ about, if you have configured clean URLs with a path format. How it works... We connect the external action named CViewAction that simply tries to find a view named the same as the $_GET parameter supplied. If it is there, it displays it. If not, then it will give you a 404 not found page. There's more... There are some useful CViewAction parameters we can use. These are listed in the following table: Parameter name Description basePath This is a base path alias that is prepended to a view name. The default is pages. That means a page named faq.company will be translated to protected/views/pages/faq/company.php. defaultView This is a name of a page to render when there is no $_GET parameter supplied. The default is index. layout The layout used to render a page. By default, the controller layout is used. If it is set to null, then no layout is applied. renderAsText If set to true, then the page will be rendered as is. Otherwise, the PHP inside will be executed. viewParam The name of the $_GET parameter used to pass the page name to CViewAction. The default is view. Further reading For further information, refer to the following URL: http://www.yiiframework.com/doc/api/CViewAction See also ff The Using external actions recipe Chapter 2 63 Using flash messages When you are editing a model with a form, when you are deleting a model, or doing any other operation, it is good to tell users if it went well or if there was an error. Typically, after some kind of action, such as editing a form, a redirect will happen and we need to display a message on the page we want to go to. However, how do we pass it from the current page to the redirect target and clean up afterwards? Flash messages will help us. Getting ready Set up a new application using yiic webapp. How to do it... 1. Let's create a protected/controllers/WebsiteController.php controller as follows: class WebsiteController extends CController { function actionOk() { Yii::app()->user->setFlash('success', 'Everything went fine!'); $this->redirect('index'); } function actionBad() { Yii::app()->user->setFlash('error', 'Everything went wrong!'); $this->redirect('index'); } function actionIndex() { $this->render('index'); } } 2. Additionally, create the protected/views/website/index.php view as follows: user->hasFlash('success')):?>
user->getFlash('success')?>
Router, Controller, and Views 64 user->hasFlash('error')):?>
user->getFlash('error')?>
3. Now, if we go to http://example.com/website/ok, we'll be redirected to http://example.com/website/index and a success message will be displayed. Moreover, if we go to http://example.com/website/bad, we will be redirected to the same page, but with an error message. Refreshing the index page will hide the message. How it works... We set a flash message with Yii::app()->user->setFlash('success', 'Everything went fine!'), for example, calling CWebUser::setFlash. Internally, it saves a message into a user state, so at the lowest level, our message is being kept in $_SESSION until Yii::app()->user->getFlash('success') is called and the $_SESSION key is deleted. There's more… The following URL contains an API reference of CWebUser and will help you to understand flash messages better: http://www.yiiframework.com/doc/api/CWebUser Using the controller context in a view Yii views are pretty powerful and have many features. One of them is that you can use controller context in a view. So, let's try it. Getting ready Set up a new application using yiic webapp. Chapter 2 65 How to do it... 1. Create a controller as follows: class WebsiteController extends CController { function actionIndex() { $this->pageTitle = 'Controller context test'; $this->render('index'); } function hello() { if(!empty($_GET['name'])) echo 'Hello, '.$_GET['name'].'!'; } } 2. Now, we will create a view showing what we can do:

pageTitle?>

Hello call. hello()?>

widget('zii.widgets.CMenu',array( 'items'=>array( array('label'=>'Home', 'url'=>array('index')), array('label'=>'Yiiframework home', 'url'=>'http://yiiframework.ru/'), ), ))?> 3. In order to test it you can follow /index.php?r=website/index. How it works... We are using $this in a view to refer to a currently running controller. When doing it, we can call a controller method and access its properties. The most useful property is pageTitle, which refers to the current page title. There are many built-in methods that are extremely useful in views such as renderPartials and widget. There's more… The following URL contains API documentation for CController where you can get a good list of methods you can use in your view: http://www.yiiframework.com/doc/api/CController Router, Controller, and Views 66 Reusing views with partials Yii supports partials, so if you have a block without much logic that you want to reuse or want to implement e-mail templates, partials are the right way to go about this. Getting ready 1. Set up a new application using yiic webapp. 2. Create WebsiteController as follows: class WebsiteController extends CController { function actionIndex() { $this->render('index'); } } 3. Set up a database using the following SQL: CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL auto_increment, `name` varchar(200) NOT NULL, `email` varchar(200) NOT NULL, PRIMARY KEY (`id`) ); 4. Use Gii to generate the User model. 5. Add some data to the user table. How to do it... We will start with a reusable block. For example, we need to embed a YouTube video on several website pages. Let's implement a reusable template for it. 1. Create a view file named protected/views/common/youtube.php and paste an embed code from YouTube. You will get something like the following: Chapter 2 67 2. Now, we need to make it reusable. We want to be able to set the video ID, width, and height. Let's make the width and height optional, as follows: 3. Now, you can use it in your protected/views/site/index.php file like this: renderPartial('//common/youtube', array( 'id' => '8Rp-CaIKvQs', // you can get this id by simply looking at video URL 'width' => 320, 'height' => 256, ))?> Looks better, right? Note that we have used // to reference a view. This means that Yii will look for a view starting from protected/views, not taking the controller name into account. 4. Now, let's send some e-mails. As we are unable to write unique letters to thousands of users, we will use a template but will make it customized. Let's add a new method to protected/controllers/SiteController.php as follows: class SiteController extends CController { public function actionSendmails() { $users = User::model()->findAll(); foreach($users as $user) { $this->sendEmail('welcome', $user->email, 'Welcome to the website!', array('user' => $user)); } echo 'Emails were sent.'; } function sendEmail($template, $to, $subject, $data) { mail($to, $subject, $this->renderPartial ('//email/'.$template, $data, true)); } } Router, Controller, and Views 68 5. Here is our template, protected/views/email/welcome.php: Hello name?>, Welcome to the website! You can go check our new videos section. There are funny raccoons. Yours, Website team. 6. That's it. In order to test it, go to /index.php?r=site/sendmails. Note that you need to configure PHP to be able to send e-mails. Instead you can just echo the result without actually sending it. How it works... CController::renderPartial does the same template processing as CController::render, except the former does not use layout. As we can access the current controller in a view using $this, we can use its renderPartial to use a view within another view. renderPartial is also useful when dealing with AJAX as you don't need the layout rendered in this case. There's more… For further information, refer to the following URL: http://www.yiiframework.com/doc/api/CController/#renderPartial-detail See also ff The Using the controller context in a view recipe Using clips One of the Yii features you can use in your views is clips. The basic idea is that you can record some output and then reuse it later in a view. A good example would be defining additional content regions for your layout and filling them elsewhere. Getting ready Set up a new application using yiic webapp. Chapter 2 69 How to do it... 1. For our example, we need to define two regions in our layout: beforeContent and footer. Open protected/views/layouts/main.php and insert the following code line just before the content output (): clips['beforeContent'])) echo $this->clips['beforeContent']?> Then, insert the following into
还剩407页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

dcn2

贡献于2013-12-22

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf