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

Print HTML and CSS to PDF with PhantomJS from PHP

On and off over the last few months, we have been searching for a solution to print a webpage, formatted correctly by a print CSS file, into a PDF file.  One of our web projects uses print CSS heavily in order for users to have a “pretty” view of content.  The trouble was that some of those pages, by necessity, were hundreds of pages long, with some rather complex/heavy CSS formatting.  This caused some clients with old/slow printers to complain, citing very long print times.  Upon further inspection at the client’s sites, we found that the printers were choking on the data.  We didn’t want to split up the pages, or radically change this workflow, so we embarked on a journey to find a way to easily create PDF files of these troublesome pages.

Being a PHP shop, we did a quick search for ‘php html to pdf’, which gave us plenty of results.  It seemed that the leading product in this area was wkhtmltopdf.  However, after playing around with the product and attempting an installation on an Ubuntu server, we saw a lot of dependencies being installed on the machine, and encountered numerous errors trying to generate the PDF.  There were other concerns regarding the age and activity level of the project.  We had one strong contender in wkhtmltopdf, but decided to continue researching.

Our attention quickly turned to PrinceXML after viewing some samples and some great reviews, but were ultimately unable to justify the rather steep server license fee.  DocRaptor was also considered, and was more reasonable, but we tend to shy away from third-party web services only because we focus on open-source software.  With limited research, these two seemed to be very good.

After all this, we discovered the excellent PhantomJS product.  The binary was easily installed on the server with no additional dependencies required, and came with a great deal of example code, including a working ‘print to PDF’ function out-of-the-box.  With some minor tweaks, we customized the script for our use case, and had a PDF copy of our webpages in minutes.  PhantomJS has been around since 2011 and is used by a variety of open source products, listed on their website.  In a later post, we will detail how we integrate PhantomJS with Yii, expanding on the PhantomJS Yii extension.

Read More

Yii CJuiSliderInput On Slide Event

As part of a new web form, we wanted to use the JUI Slider control in our Yii project.  Luckily, the framework includes this JUI widget, and we had a working first version within minutes.  However, as we tried to fulfill the requirement of having a textbox value change as the slider was being dragged, we found that our ‘slide’ event was not firing.  This is because the slide event is already being used by the Yii widget, but there is a way to use the slide event. Simply pass in ‘event’ => ‘change’ into the widget, and the slide function is now available for use. We hope this saves someone else a little time!

$this->beginWidget('zii.widgets.jui.CJuiSliderInput', array(
    'name' => $id,
    'value' => 0,
    'id' => $id,
    'event' => 'change',
    'options' => array(
        'min' => 0,
        'max' => $max,
        'slide' => 'js:
            function(event, ui) {
                $("#Textbox").val(ui.value);
            }
        ',
    ),
));
$this->endWidget('zii.widgets.jui.CJuiSliderInput');

Read More

Yii CEmailValidator now checking MX records

Yii 1.1.11 was released a few weeks ago, sporting a number of bug fixes and enhancements.  One improvement in particular caught my eye.  In the past, if you used the checkPort functionality in CEmailValidator, it wouldn’t always work as intended.

For instance, Hollow Developers uses Google Apps to manage our mail server, so we have no mail server listening on our domain.  CEmailValidator’s old processing would fail for our domain because the validation was very simple – it only looked to see if a mail server was up and running on the email address’ domain.  As more and more domains use third-party services for mail, more and more domains would fail.  Obviously, this rendered this validation as fairly useless.  As of 1.1.11, however, the checkPort functionality is smart enough to look at the domain’s DNS MX records and look for a mail server at those locations.

We wanted to write a blog post about this since there are a few comments at the bottom of the CEmailValidator page that are out of date, and may confuse some people.  We hope this helps someone so they won’t have to dig through the release notes or validation source code.

Read More

Improving the DateTimePicker Yii Extension

The Yii extension DateTimePicker is a great extension when you want a user to be able to select a date and time within the same popup for an input.  We have recently started to use it extensively for dates & times that occur in future months.  We ran into a problem, however, in that the date would default to today’s date – a real headache for the user.  The root cause of the problem is that the Yii extension’s jQuery files are a few versions behind.  To fix it, you can upgrade the ‘assets’ folder with the new version of Trent Richardson’s jQuery-TimePicker-addon extension.  Here’s a step-by-step:

  1. Download the zipped jQuery-TimePicker-addon from github
  2. Extract the zipped contents into your protected/extensions/timepicker/assets folder
  3. Update the minified jQuery file.  You can either copy the exact same code from the main js file into this file, or you can minify it using one of the many easy-to-use JavaScript minifiers.
  4. Test it out!

Read More

Yii Framework Easy Test Fixture Creation

We’ve said it before, and we’ll say it again – we are huge fans of the Yii Framework.  This PHP framework is easy to understand, and even better, easy to create test cases.  One of the most time consuming portions of writing the test suite, however, was getting your test data into Yii’s ‘fixture’ files.  These fixture files correspond to data that would be in your database tables, but the files stay constant so you always have a known start state.  That way, you can test your application easily at any time, ensuring that your recent build hasn’t broken any legacy functions.

Due to the fixture files, we had resorted to running a custom script that took data out of our production database and exported it to a fixture file.  However, with a recent new installation of phpMyAdmin, we came across the PHP Array plugin.  Using this plugin, you can easily export your table data into PHP array format – the same format that is needed for your Yii fixture files.

We hope this saves someone some time and hassle down the road!

Read More

Yii Framework Separate Configurations for Different Environments

Yii doesn’t have a built-in way of changing configurations based on the environment that it is running in. However, there are a number of ways to accomplish this.

Method #1

There is a great extension available that allows you to use different configuration files based on the environment that is found in the Apache configuration file.  Yii-environment does require modification of these files, so the most basic hosting plan may not be able to accommodate the extension.  However, as is true with any programming task, there is more than one way to skin a cat.

Method #2

Another method for differentiating environments is to edit the index.php file. Based on a PHP server variable, a different configuration file can be used.

switch ($_SERVER['SERVER_NAME']) {
    case "development":
        $config=dirname(__FILE__).'/protected/config/development.php';
        break;
    default:
        $config=dirname(__FILE__).'/protected/config/production.php';
        break;
}

To make things easier, you can just include the variables that will differ in your development/production configuration files. A third file can be used to hold all shared variables. For instance, your shared configuration file can look like:

return array(
    'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',
    'name' => 'Application Name',
    // autoloading model and component classes
    'import' => array(
        'application.models.*',
        'application.components.*',
    ),
    // application components
    'components' => array(
        'user' => array(
            // enable cookie-based authentication
            'allowAutoLogin' => false,
        ),
   ),
);

Then, each of the environment-specific variables can go into the development or production.php configuration files.

return CMap::mergeArray(
        require(dirname(__FILE__) . '/shared.php'),
        array(
            'components' => array(
                'db' => array(
                    'connectionString' => 'mysql:host=mysql;dbname=databaseName',
                    'emulatePrepare' => true,
                    'username' => 'user',
                    'password' => 'password',
                    'charset' => 'utf8',
                ),
            ),
     )
);

Read More