I apologise to all those of you who have been patiently waiting for the next installment of my series on Yii2 (see Part 1). I've been busy developing wordpress eCommerce systems over the summer and had little time to get back into Yii2. In the meantime, Yii2 has gone live and I'm sure many of you are busy using it full-time now.
Moving from Yii1 to Yii2 is a big step not to be taken lightly. From the basic application architecture through to the Active Record and Query builder objects, views, themes, widgets, asset management and aliases, changes are considerable and, as you might expect, more powerful.
A summary of the changes
Namespaces
If you're not familiar with namespaces then this is going to be a big shock for you and has taken me quite some time to get used to. The concept of namespaces itself is not too complicated, it is the fact that autloading seems to have gone out of the window.
This is one of the things I really like about Yii1.x – the fact that you didn't need to understand the complex geometry of the the Yii code in order to get up and running fairly quickly. Furthermore, it was my impression that the latest generation of frameworks were designed to separate these complexities from the everyday application developer.
It's only when you want to extend the Yii system that you needed to start understanding the classes and components under the bonnet. Not every racing driver understands what happens under the hood!
With Yii2, you need to have a map of where every class that you might need lives. for example our simple data model that in Yii1.x was based on the CActiveRecord object now becomes
namespace appmodels; use Yii; /** * This is the model class for table "albums". * * @property integer $id * ... */ class Album extends yiidbActiveRecord
That's all fine and dandy when you're working on a class generated by Gii but how do you know what classes you need to refer to and where they are. It seems to me that it's just experience. There is no easy route or cheat sheet … you just need to know. If I'm out of line here then please feel free to correct me OR if there is a nice easy solution then please let me know! It would save a lot of readers a heck of a lot of pain, I'm sure.
So what this means is that in your controllers, if you're using a diverse set of controls, referencing a number of data model classes you are likely to end up with a long list of use statements such as this:-
namespace app/controllers; use Yii; use yii/filters/AccessControl; use app/models/Gallery; use app/models/GallerySearch; use app/models/PriceList; use yii/web/Controller; use yii/web/NotFoundHttpException; use yii/filters/VerbFilter; use yii/web/Response; use yii/widgets/ActiveForm;
Component and Object
The CComponent class from Yii1.x has been divided into 2 separate classes, the Object class and the Component class. If you're not that familiar with the CComponent class it was one of the base classes in Yii1.x responsible for the implementation of getter and setter methods for object properties and also events.
Getter and Setter methods meant that you didn't have to define get and set methods for each property in your classes (mostly data models) but you could over-ride them using getPropertyName or setPropertyName in your classes.
Events are methods starting with 'on'. When an event is raised, functions attached to the event will be called. You may have come across these more using the beforeSave and afterSave events in data model classes.
See Yii1.x events and behaviors: http://www.yiiframework.com/wiki/44/behaviors-events
Yii2 – if you are not planning to use events or behaviors in your class it is recommended to derive it from the Object class
Path Aliases
Path Aliases now use an @ prefix and can be used for both paths and URLs. You will start using custom paths for each project and can also setup extension bundles (more later) with extensive use of custom paths.
There are a number of pre-defined aliases within Yii2 as follows:-
- @yii – the directory where the BaseYii.php file is located (also called the framework directory)
- @app – the [[yiibaseApplication::basePath|base path]] of the currently running application
- @runtime – the [[yiibaseApplication::runtimePath|runtime path]] of the currently running application. Defaults to @app/runtime.
- @webroot – the Web root directory of the currently running Web application. It is determined based on the directory containing the entry script.
- @web – the base URL of the currently running Web application. It has the same value as [[yiiwebRequest::baseUrl]].
- @vendor – the [[yiibaseApplication::vendorPath|Composer vendor directory]]. Defaults to @app/vendor.
- @bower – the root directory that contains bower packages. Defaults to @vendor/bower.
- @npm – the root directory that contains npm packages. Defaults to @vendor/npm.
Views
Views have been given their own object and thus there are a number of differences using controllers and views.
- Not directly connected, but the $this->render method does noit echo. So now you need to write echo $this->render(…)
- Within a view, $this no longer refers to the controller object but to the view object, as you might expect. To access the controller you can use $this->context
- Now includes clientscript, $this->registerScript("",View::POS_READY);
- registerJS – has removed first parameter
- Listview and other widgets – now uses $model rather than $data
- renderPartial has been removed. You now use just render.
- You can now use other templating engines such as Smarty and Twig instead of basic PHP.
Models
There are a large number of changes in the area of data models. For me one of the most exciting is the new Query Builder object which I haven't yet used in earnest but its going to simplify some of my search methods hugely.
The base class for models is now called just Model and can be found using yiibaseModel. The way that you setup validation scenarios has changed to sue a new method called scenarios where you define in an arrya which scenario an attribute needs to be validated and which can be considered as safe. see the documentation for examples
Active Record and Query Builder
I've lumped these two objects under one heading as, for me, this is one of the areas of Yii2 that are really exciting. When working with complex data relationships, where one table may have many related tables, using the new active record object you can build functions that return a query object and then combine these into one query.
The basic query builder is much easier to use than in Yii1. Here as some simple examples:-
// to return an *active* customer whose ID is 1: $customer = Customer::findOne([ "id" => 1, "status" => Customer::STATUS_ACTIVE, ]); // to return customers whose ID is 1, 2 or 3: $customers = Customer::findAll([1, 2, 3]);
Working with scopes is also based on the active query object. Take an example of a photo sharing website where you setup different galleries to hold collections of photos. In the backend you will only want a user to be able to access his own galleries. In Yii1 we could have done this using scopes. In Yi22 we set this up using a new class based on the ActiveQuery object as follows
class AlbumQuery extends ActiveQuery { public function mine() { $owner_id=Yii::$app->user->id; $this->andWhere(["owner_id" => $owner_id]); return $this; } } ... $myAlbums=Album::find()->mine()->findAll();
Relations have been changed and are now represented using functions
namespace appmodels; use Yii; use yiihelpersHtml; /** * This is the model class for table "photos". * */ class Photo extends yiidbActiveRecord /** * @return yiidbActiveQuery */ public function getAlbum() { return $this->hasOne(Album::className(), ["id" => "album_id"]); }
Since this also returns an active query object it can be used in the query builder also
$myAlbums=Album::find()->mine()->findAll(); $numberPhotos=count($myAlbums->photo); ... }
now for the fun bits…
Sometimes, particularly when building search results we need to do something more on the fly. rather than using dynamic SQL, which particularly with sub-queries was really the only way in Yii1.x, we can use the active query object to join and build sub-queries
$myAlbums=album::find()->innerJoinWith([ "gallery", "photo" => function ($query) { $query->where(
In our data model we need to refer to this scope
namespace appmodels; use Yii; /** * This is the model class for table "albums". * ... */ class Album extends yiidbActiveRecord { /** * @inheritdoc */ public static function tableName() { return "albums"; } /** * @inheritdoc * @return AlbumQuery */ public static function find() { return new AlbumQuery(get_called_class()); }
You can then use this in your query builder statements as follows:
$myAlbums = Album::find()->mine()->all();