Starting with Yii2 - part 2

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 app\models;
use Yii;
/**
 * This is the model class for table "albums".
 *
 * @property integer $id
 * ...
 */
class Album extends \yii\db\ActiveRecord

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;

Showing my age now, this harps back to my Cobol compiler days when you had to include all your libraries in a 'link list' file in order for the compilation to work.  It seems a massive step backwards and I would love to find a better/simpler solution..

Perhaps we could have a beginners mode where autoload is automatic, if albeit slower.

Or even a pre-compiler kind of linker which adds the required libraries for you ...

Enough of that rant now, moving on ...

 

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 [[yii\base\Application::basePath|base path]] of the currently running application
  • @runtime - the [[yii\base\Application::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 [[yii\web\Request::baseUrl]].
  • @vendor - the [[yii\base\Application::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.

  1. Not directly connected, but the $this->render method does noit echo.  So now you need to write echo $this->render(...)
  2. 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
  3. Now includes clientscript, $this->registerScript("",View::POS_READY);
  4. registerJS - has removed first parameter
  5. Listview and other widgets  - now uses $model rather than $data
  6. renderPartial has been removed.  You now use just render.
  7. 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 yii\base\Model.  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 Yii2 we set this up using a new class based on the ActiveQuery object using a customized query class 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();

Notice the use of andWhere.  This is very important as if you used just $query->where you could over-write any preceeding where condition - whereas andWhere adds to the where condition $query->where over-writes it.

Relations have been changed and are now represented using functions

namespace app\models;

use Yii;
use yii\helpers\Html;

/**
 * This is the model class for table "photos".
 *
 */
class Photo extends \yii\db\ActiveRecord
    /**
    * @return \yii\db\ActiveQuery
    */
    public function getAlbums()
    {
        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([
    "galleries",
    "photos" => function ($query) {
	$query->andWhere(
             'lastupdate_dt' < now() -30;
    }
   ])->mine()->all();

Firstly, notice that the innerJoinWith is using relation names which correspond to the function defining that relation.  You will often find here that the practice is to use plurals to distinguish between the album model and the albums relation.

Secondly, that using the relation means that we don't need to specify the join conditions as these are already defined within the relation.

We will use this style of sub-query extensively further on down the development.

We then used the "scope" called mine() to filter the query.

Before we can use our "scope", we need to get the album model to reference it.  To do that, you tell the Album model to use this new query object when you execute the model-find() method by over-riding the find() method in the Album model.

namespace app\models;

use Yii;

/**
 * This is the model class for table "albums"
 * ...
 */
class Album extends \yii\db\ActiveRecord
{
    /**
     * @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();

 

To be continued ...

Did you know you can hire me?

I take on projects of all sizes. From Consulting to large Development Projects.

If you're starting a new Yii project and would like some help to get setup and running or you need some help with a particular module or you just need someone to develop the whole dang thing, then just ask ...


Leave a Comment

twitterfacebookgooglelinkedin https://me.yahoo.com