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

Cryptic MySQL Error 1025 on Drop of Foreign Key

Mysql_manager_logo

Over the years, we have inherited a number of databases, most utilizing MySQL.  Modifying those tables can lead to some odd situations, receiving errors that we shouldn’t, and usually with MySQL, receiving cryptic errors.  One recent database upgrade had us scratching our heads for a few minutes –

This database had foreign keys using the format ‘fk_keyName’.  All of those indexes had foreign keys, and we had upgraded numerous other tables in this database, dropping some foreign keys and replacing them with an optimized schema.  However, upon dropping one particular foreign key, we received the following error:

General error: 1025 error on rename of ‘.\databaseName\tableName’ to ‘.\databaseName\#sql2-aaa-aaa’ (errno:152)

After double-checking the index name and confirming it was correct, we ran ‘SHOW CREATE TABLE’, and noticed that although this foreign key was an index, it did not have a foreign key constraint associated with it.  So in this one instance, the foreign key was only an index, most likely a mistake by the original authors.

Hopefully, this will save just a few minutes for someone out there, or save us a few minutes if/when we run into the situation again.

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

MediaWiki / Amazon ELB / CloudFlare – IP Addresses

Using an out-of-the-box MediaWiki installation in the Amazon EC2 environment behind an Elastic Load Balancer and CloudFlare as a DNS provider, every user’s IP address will show as the load balancer’s IP address.  For obvious reasons, this is not ideal, but is easy to remedy.

First, add $wgUsedPrivateIPs = true; to your LocalSettings.php if not already present.

Then, the TrustedXFF MediaWiki extension needs to be installed, and the CloudFlare IP addresses should be added to the trusted-hosts.txt document.  The extension handles the IP range syntax used by CloudFlare, so a direct copy of the IPv4 text file is sufficient.  In addition, add your elastic load balancer IP address/range so that proxy will also be whitelisted.

Edit generate.php – find the location where a “Range too big” error could be thrown.  On the line above this, change the 8192 limit to 132192.  This will ensure all CloudFlare IP addresses are added.  If you run into performance problems later, start removing the lesser-used ISPs from the bottom of the trusted-hosts.txt document.

If your server is not live, add the require_once line to your LocalSettings.php.  Then, run the generate.php script that came with the extension.

If your server is live, you will need to specify the installation location on the command line (mediawiki_directory/cache/trusted-xff.cdb by default).

The generate.php file will create a database of IP addresses that will be used as trusted proxies.  Since you added CloudFlare’s IP addresses, you should now see the actual IP address of the user.

You may consider running generate.php periodically to receive updated IP addresses.

The procedure above allows you to get the actual IP address of the user without modifying core MediaWiki code, as the CloudFlare FAQ suggests.

Read More