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

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

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

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

How Rackspace Can Turn It Around

Rackspace reported their first quarter earnings Wednesday, and the market did not like what it reported.  At market close Thursday, the stock had suffered nearly a 25% loss.  At the same time, there was some good news in that revenue is up 20%.

Hollow Developers has always been a great fan of Rackspace, and we use their cloud server products as our failover systems.  However, we were forced to other cloud server providers for our everyday hosting needs primarily due to cost.  Amazon Web Services (AWS) has the hefty weight of Amazon behind it, allowing AWS to adopt Amazon’s aggressive pricing models, undercutting prices of competitors by a fairly substantial margin.  There has always been a premium cost to do business with Rackspace, and this premium is definitely worth it for their ‘fanatical’ customer service.  However, there are some businesses that cannot afford that premium, or don’t depend on the customer service enough in order to rationalize paying the premium.  In our case, interaction with customer service is so limited that a monthly premium doesn’t make sense.

So, how does Rackspace turn things around?

First, place benchmarks on their cloud servers instead of relying on third-party websites to compare their CPUs against Amazon’s.  In our experience, AWS virtual machines don’t have the CPU horsepower that some would expect.  If Rackspace doesn’t exceed Amazon’s performance for comparably priced servers, bump up virtual machine resources until they do.  This metric is the most important for many websites that rely heavily on dynamic pages, and can become a deciding factor in choosing one service over another.  It is not immediately apparent that Rackspace’s entry-level server actually outperforms Amazon’s closest alternative when comparing CPU and price.

Second, allow more administrative actions to be performed via the Rackspace control panel.  A Scalr-like setup for auto-scaling and easier cross-region deployments would be a huge value-add to the product.  With Scalr running $100/month, some basic built-in scaling could be a deciding factor in using Rackspace versus a competitor.  Alternatively, partner with Scalr to provide some sort of discount to the service.  Rackspace customers already receive discounts at a number of other websites, and placing a cheaper Scalr monthly cost on to the table may be a deciding factor for those wanting to grow their cloud footprint.

Finally, take a page out of Amazon’s book and add a free or nearly-free tier to Cloud Servers.  Allowing people to sample the product, even for a few months, would invariably lead to some people remaining on the platform instead of seeking out competitors.  Pairing this with a Scalr partnership would make Rackspace that much more attractive.

Surely, there are other things that Rackspace could do to regain the confidence of investors, but these seem to be the most important among small businesses with small but expanding cloud footprints like ours.

* Disclaimer – Hollow Developers is a small investor, customer, and former affiliate of Rackspace.

Read More

Amazon Elastic Load Balancer on a root domain

Over the past few months, Hollow Developers has migrated servers into the Amazon EC2 environment. As part of this setup, a load balancer redirects traffic to a number of individual EC2 web server instances.  A limitation to this, however, is that Amazon’s load balancers don’t work on root domains (for example, http://hollowdevelopers.com/, no www in front).  The reason that these load balancers don’t work on root domains is because the DNS record must be a CNAME record, and not an A record.  And, root domains at most DNS providers only allow A records.

CNAME and A Records: CNAME entries allow domains to create subdomains like ‘webmail.hollowdevelopers.com’, which can act as an alternative address to something like ‘google.com/a/hollowdevelopers.com’ – the CNAME record makes that long URL at another domain easy to remember.  A records only allow IP addresses.  Amazon Load Balancers require an entry like ‘hollowdevelopers-load-balancer.ec2.amazon.com’, so a CNAME entry is required.

So, this ultimately requires websites to use ‘www’ or something similar in front of their domain, since the ‘www’ record can be a CNAME record.  As part of their sales pitch for their Route 53 DNS service, Amazon mentions that Route 53 allows you to place CNAME-type records into your root domain.  However, we have always been happy with our DNS provider, CloudFlare.  So, what is an easy way to ensure that all traffic goes through our load balancer?

On first glance, Hollow Developers was OK – our web servers automatically redirect users from the root domain to the www domain, primarily for consistency for search engine crawlers.  However, in order for this to happen, the user would have already hit our server on the root domain.  We wanted all traffic to go through the load balancer, regardless of the small number of hits that may come in through the root domain.  This is where CloudFlare’s page rules came in.

CloudFlare page rules allow website owners to write redirect rules, allowing all traffic from the root domain to redirect to the www domain.  Best of all, even free CloudFlare accounts allow a few page rules, meaning that anyone can use this trick for a free alternative to Amazon’s Route 53.  Just a few rules will get you up and running:

  • Forward http://hollowdevelopers.com/* to http://www.hollowdevelopers.com/$1
  • Forward http://hollowdevelopers.com/ to http://www.hollowdevelopers.com/

The first rule will forward all pages on the domain to the exact same page on www.  The second rule forwards the ‘naked’ root domain to the www domain.  For more information on the syntax used, consult the CloudFlare documentation on the Page Rules interface.

There are numerous alternatives to this approach – including the use of Amazon’s Route 53 DNS service.  However, we wanted to keep CloudFlare’s security and DDOS prevention features, so this was not an option we wanted to take.  Have other alternatives?  We would love to hear your comments/questions.

Read More