Yii 2.0 Widget Content At Top of Page – Quick Tip

Quick Yii 2.0 debugging tip:

We recently made use of some Yii 2.0 widgets in a project being converted from Yii 1.1.  On one page, we had a JUI Progress Bar widget being displayed as well as an ActiveForm widget.  On initial tests, the progress bar (the first widget on the screen) was being printed at the very top of the page.  A look at the HTML source revealed it was echo’ing the progress bar HTML at the very top of the HTML, even before the first tag.  Looking at the page a little closer, a mistake was found – the ActiveForm widget didn’t have an end tag.  Adding the end tag fixed it up.

We hope this saves someone some time down the road!

Read More

Yii 2.0 ActiveRecord inverseOf

Yii 2.0 introduced a new feature to its ActiveRecord class, the inverse relation.  The documentation does a decent job at explaining its purpose – to prevent an unneeded SQL call from being executed and the instantiation of a new object, when in reality, the current object that is performing the lookup is the same object that would be found with that unneeded SQL call.  The optimization is much appreciated and in one project, this new relation has cut down on the SQL being executed and potential errors being introduced.

However, what the documentation does not make clear, as referenced in issue 7316, is that, in most cases, the inverse relation should only be defined for one side of the relationship.  For example, let’s take the documentation’s example of an order having exactly one customer, and customers having many orders.  In this example, the inverseOf should only be defined within the Customer class’ order relation.  This is because the order can only have one customer, so no SQL is required to determine what the correct customer will be.

What if we had an inverseOf relation defined in the opposite direction from the Order class’ customer relation?  The inverseOf relation would tell the customer class that this one order is the only order the customer has!  Obviously, not correct.

To determine if you should use the inverseOf relation, just ask yourself when writing the relation, does the other class have a ‘hasOne’ relation to this class?  If it does, you should be OK to use the inverseOf relation.

Read More

Yii 2.0 Pjax Advanced Use & Lessons Learned

Yii Framework 2.0 ships with built-in support for Pjax, a JavaScript library that reduces page load times. It accomplishes this by only updating the part of the page that has changed through Ajax, which can translate into substantial savings if you have many other assets on your pages. A few of our projects use this functionality and we wanted to share some lessons learned.

Problem: Page 1 is a simple static page that contains few elements. Page 2 includes an ActiveForm as well as other widgets. The ActiveForm JavaScript resources need to be loaded in order for the inline JavaScript to run, but since Page 1 did not include those assets, Page 2 ran into a JavaScript error when trying to execute the activeform line: ‘Uncaught TypeError: undefined is not a function’.

Solution: Include ActiveForm assets in a shared asset bundle that will be loaded across all pages, ensuring that any entry page will allow the correct scripts to be available.

class AppAsset extends AssetBundle
{
    ...
    public $depends = [
        'yii\widgets\ActiveFormAsset',
        'yii\validators\ValidationAsset',
    ];
    ...
}

Problem: In the same example above, Page 1 includes a few widgets (NavBar, etc.). Page 2 includes the same widgets plus a few more (ActiveForm, etc.). When loading the page via Pjax, some custom inline JavaScript was running, but the inline script placed by the ActiveForm widget didn’t seem to work, as the validation code was not working. In debug, we found that the ActiveForm init function was running, but the ‘this’ variable didn’t seem to correspond to the ActiveForm. It actually corresponded to the NavBar div. Investigating the div IDs, we saw that the ActiveForm was expecting to have the ID of #w1, but the NavBar was already assigned that ID on the Page 1 since that was the first widget encountered on that page.

Solution: Do not rely on Yii to auto-generate the widget IDs for you. Instead, always pass in an ID when creating the widget to maintain control of those IDs.

Problem: Pjax request was getting canceled exactly 1,000 ms after the request was initiated.

Solution: Increase the Pjax timeout setting. It defaults to 1 second, which should be acceptable for production sites. However, in development, while using xdebug, our page load times are regularly over this limit.

Problem: Web application implements the Post-Redirect-Get (PRG) pattern. Pjax reloads entire page instead of just the redirection.

Solution: This is intended behavior of Pjax. The redirect doesn’t serve its purpose when using Pjax, so you can determine if a request is Pjax, and if so, render the content instead of redirecting. An example may look like:

$endURL = "main/endpoint";
if (Yii::$app->request->isPjax) {
    return $this->run($endURL);
} else {
    return $this->redirect([$endURL]);
}

What has your experience been with Pjax and Yii? Comment below if you’ve found any gotchas or have better solutions than ours!

Read More

Yii 2.0 – Where did ActiveRecord Scopes go?

The Yii Framework team recently released their second major version, 2.0.  The version is still a little rough around the edges, but my first impression is that it is a solid replacement for their last major version, 1.1.

ActiveRecord has been given a facelift in 2.0, and one of the 1.1 features – scopes – isn’t mentioned until the very end of the ActiveRecord guide page.  While it was convenient to declare scopes in 1.1 to filter results down to the ones that matter to you at the moment, it also mixed much query logic inside the model.  The developers made the decision to require the use of a separate ActiveQuery class if you want to use a scope-style pattern.

In practice, there isn’t much new work here – just another class that you need to create and an override of your model’s find function.  Check out the pattern below, as detailed in the Yii documentation.

namespace app\models;

use yii\db\ActiveRecord;
use yii\db\ActiveQuery;

class Comment extends ActiveRecord
{
    public static function find()
    {
        return new CommentQuery(get_called_class());
    }
}

class CommentQuery extends ActiveQuery
{
    public function active($state = true)
    {
        return $this->andWhere(['active' => $state]);
    }
}

Read More