Why to version your composer.lock file

Composer.json often lists dependencies with version numbers that can point to a bunch of potential packages. Lines like this:

"require": {
    "laravel/framework": "4.2.*",
    "ruflin/elastica": "1.3.0.0",
    "guzzlehttp/guzzle": "~4.0",
    "sunra/php-simple-html-dom-parser": "1.5.0"
},

When composer install is run without a composer.lock file available, it has to translate the ~, *, =>, and other bits of flexible version numbering into an real version number to download for you. It also has to grab the dependencies of each one of the dependencies you specify. This takes time, memory and bandwidth. When composer finishes, it saves a list of the packages it has downloaded to composer.lock with real version numbers and all the required packages included.

Example Scenario

You start a new project by yourself. You set up composer.json with some packages, run composer install and commit all the right stuff to get, minus composer.lock

72 hours later

Another developer on your team receives your code, including the composer.json. They run composer install and receive a different set of packages than you did 72 hours earlier because one of the dependencies pushed an update. Will that be a problem? Maybe…

72 hours later

The project is done! Time to deploy. Somewhere in the process composer install runs. All the dependencies will be resolved again wasting bandwidth, memory, time, and potentially installing a different set up of packages then on either development machines. This won’t introduce bugs if you are lucky, but it isn’t optimal.

What’s inside composer.lock?

You’ll notice composer.lock is a much bigger file than composer.json (72kb vs 1kb for my example). Here’s what it looks like:

{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "hash": "675d6a1de44ac07f57f730785f25142b",
    "packages": [
        {
            "name": "classpreloader/classpreloader",
            "version": "1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/mtdowling/ClassPreloader.git",
                "reference": "2c9f3bcbab329570c57339895bd11b5dd3b00877"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/mtdowling/ClassPreloader/zipball/2c9f3bcbab329570c57339895bd11b5dd3b00877",
                "reference": "2c9f3bcbab329570c57339895bd11b5dd3b00877",
                "shasum": ""
            },
            "require": {
                "nikic/php-parser": "~0.9",
                "php": ">=5.3.3",
                "symfony/console": "~2.1",
                "symfony/filesystem": "~2.1",
                "symfony/finder": "~2.1"
            },
            "bin": [
                "classpreloader.php"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "ClassPreloader": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case",
            "keywords": [
                "autoload",
                "class",
                "preload"
            ],
            "time": "2014-03-12 00:05:31"
        },
        {
            "name": "d11wtq/boris",
            "version": "v1.0.8",
            "source": {
                "type": "git",
                "url": "https://github.com/d11wtq/boris.git",
                "reference": "125dd4e5752639af7678a22ea597115646d89c6e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/d11wtq/boris/zipball/125dd4e5752639af7678a22ea597115646d89c6e",
                "reference": "125dd4e5752639af7678a22ea597115646d89c6e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "suggest": {
                "ext-pcntl": "*",
                "ext-posix": "*",
                "ext-readline": "*"
            },
            "bin": [
                "bin/boris"
            ],
            "type": "library",
            "autoload": {
                "psr-0": {
                    "Boris": "lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "time": "2014-01-17 12:21:18"
        },
 
......[more packages listed here]....
 
    ],
    "aliases": [
 
    ],
    "minimum-stability": "stable",
    "stability-flags": {
        "mockery/mockery": 20
    },
    "prefer-stable": false,
    "platform": [
 
    ],
    "platform-dev": [
 
    ]
}

You’ll find all the packages in composer.json and the resolved dependencies listed, each with their version number. Why is this better? When composer install uses information from composer.lock, it simply downloads the listed packages, and doesn’t have to traverse through the dependency tree searching for packages, or figure out which version of a package to download. This saves time when a developer or server runs composer install, and ensures the same versions of every package are requested every time. Keep composer.lock in version control, and the team and production server will benefit from this when composer install is run.

More info

Read this conversation on GitHub:

KingCrunch commented on Feb 11

@paparts Sounds like you don’t versionize the composer.lock? As a rule of thumb: For applications versionize it, for libraries, don’t. You shouldn’t run update on a live system, because it is quite likely, that sooner or later a package comes in, that breaks your application, without that you’ve tested it locally. The composer.lock and composer.phar install ensures, that exactly that packages in that versions are installed, that you’ve development your application against.

paparts commented on Feb 12

I didn’t notice that the framework I was using has listed the composer.lock on the ignore list. Thanks for pointing that out.

Fatal error: Uncaught exception 'ErrorException' with message 'proc_open(): fork failed - Cannot allocate memory' in phar:///home/ubuntu/somefolder/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:985

Here’s an explanation of composer install and composer update and how they are different: http://adamcod.es/2013/03/07/composer-install-vs-composer-update.html.

Here’s the official composer documentation explaining what the lock file does: https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file

How to run composer.phar on Dreamhost

Dreamhost’s shared hosting offers up some challenges to get Composer’s composer.phar running.

  1. Even if your websites are setup to use PHP 5.3 or 5.4, the CLI version of php might be PHP 5.2, which is too old and doesn’t include phar. You can check this by logging into your account with a terminal via SSH and the command
    php --version
  2. Since this is a shared hosting environment, you can’t change things the way you would in own Vagrant Ubuntu box or local dev environment.

You’ll have to get through these to run Laravel 4 and other applications that rely on Composer.

GitHub user Braunson has the best walkthrough on setting up composer.phar on DreamHost that I have found. These instructions worked on my account. I did have to do the extra step of putting phprc into a 5.4 folder. After logging out of SSH and logging back in, the phar extension worked, and I could use composer.

Another way to check if phar is available is to run

php -i | grep phar

, which will pull out phar-related config info. A server with phar enabled will look like this:

Configure Command =>  './configure'  '--prefix=/usr/local/php54' '--datadir=/usr/local/php54/share' '--localstatedir=/usr/local/php54/var' '--without-pear' '--with-config-file-path=/etc/php54' '--with-config-file-scan-dir=/etc/php54/conf.d' '--with-zend-vm=GOTO' '--enable-cgi' '--enable-fpm' '--enable-libxml=shared' '--enable-bcmath=shared' '--enable-calendar=shared' '--enable-ctype=shared' '--enable-dom=shared' '--enable-exif=shared' '--enable-fileinfo=shared' '--enable-filter=shared' '--enable-ftp=shared' '--enable-hash=shared' '--enable-intl=shared' '--enable-json=shared' '--enable-mbstring' '--enable-mbregex' '--enable-mbregex-backtrack' '--enable-pcntl=shared' '--enable-pdo' '--enable-phar=shared' '--enable-posix=shared' '--enable-session=shared' '--enable-shmop=shared' '--enable-simplexml=shared' '--enable-soap=shared' '--enable-sockets=shared' '--enable-sysvmsg=shared' '--enable-sysvsem=shared' '--enable-sysvshm=shared' '--enable-tokenizer
=shared' '--enable-wddx=shared' '--enable-xml=shared' '--enable-xmlreader=shared' '--enable-xmlwriter=shared' '--enable-zip=shared' '--with-openssl=shared' '--with-kerberos' '--with-pcre-regex' '--with-sqlite3' '--with-zlib=shared' '--with-bz2=shared' '--with-curl=shared' '--with-gd=shared' '--with-jpeg-dir=/usr' '--with-png-dir=/usr' '--with-zlib-dir=/usr' '--with-xpm-dir=/usr' '--with-freetype-dir=/usr' '--with-gettext=shared' '--with-gmp=shared' '--with-mhash=shared' '--with-iconv=shared' '--with-imap=shared' '--with-imap-ssl' '--with-ldap=shared' '--with-mcrypt=shared' '--with-msql=shared' '--with-mysql=shared,mysqlnd' '--with-mysql-sock=/No-MySQL-hostname-was-specified' '--with-mysqli=shared,mysqlnd' '--with-pdo-mysql=shared,mysqlnd' '--with-pdo-pgsql=shared' '--with-pdo-sqlite' '--with-pgsql=shared' '--with-pspell=shared' '--with-readline=shared' '--with-tidy=shared' '--with-xmlrpc=shared' '--with-xsl=shared'
Registered PHP Streams => compress.bzip2, php, file, glob, data, http, ftp, http
s, ftps, zip, compress.zlib, phar
Phar-based phar archives => enabled
Tar-based phar archives => enabled
ZIP-based phar archives => enabled
phar.cache_list => no value => no value
phar.readonly => On => On
phar.require_hash => On => On

Notice all the lines towards the bottom that show phar is available.

W3 Total Cache makes blank pages after WordPress upgrade fix

Here’s the first fix I always try after W3 Total Cache starts serving blank white pages when I browse my site after updating WordPress. These instructions assume you know your way around the WordPress and W3 Total Cache admin areas.

Testing for blank pages

To test for the blank screen issue, be sure to view the site in Chrome’s Incognito mode or Firefox’s Private Browsing mode without logging into the admin area. Logging into the admin area creates cookies for storing your session, and W3 Total Cache will not serve cached pages to users who are logged in (unless you change it in W3 TC’s Page Cache configuration).

Debugging info

Also note that W3 TC will inject some extra comments into your page to give you debugging info. You can see it by viewing the source. This is a good way to check if Page Caching is enabled when you load a page. Here’s a sample:

!-- W3 Total Cache: Page cache debug info:
Engine:             disk: enhanced
Cache key:          kb.imakewebsites.ca/_index.html
Caching:            disabled
Reject reason:      User is logged in
Creation Time:      0.768s
Header info:
X-Pingback:          http://kb.imakewebsites.ca/xmlrpc.php
Expires:             Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control:       no-cache, must-revalidate, max-age=0
Pragma:              no-cache
Content-Type:        text/html; charset=UTF-8
X-Powered-By:        W3 Total Cache/0.9.2.11
X-W3TC-Minify:       On
--

Environment Details

This bug appeared for me today moving from WordPress 3.5.1 to 3.6 with W3 Total Cache 0.9.2.11 on a DreamHost server (I’m not sure what versions of PHP and Linux and MySQL it uses). I’ve seen it happen on other hosts like an EC2 instance hosted on Amazon with Ubuntu when moving between other versions of WordPress as well.

A solution

In my experience, the blank pages are always caused by the Page Caching functionality (with Disk: Enhanced caching method).  Try following these steps to get your pages back:

  1. Save your W3 TC configuration. At the bottom of the plugin’s Dashboard you’ll find tools for exporting your configuration. Do it so you have a backup.
  2. Disable the W3 TC plugin
  3. Delete the W3 TC plugin
  4. Visit your site and verify that it works without W3 TC. If the white pages are still appearing, there might be another issue that I’m unfamiliar with.
  5. Install the W3 TC plugin.
  6. Enable the W3 TC plugin. You may find your configuration from the last install is still on the server and used automatically. If not, you have a backup from step 1!
  7. Visit your site in a Private or Incognito tab (remember the detail about cookies mentioned above). If WordPress and Page Caching is working again, you can stop following these instructions here
  8. Try disabling all W3 TC’s functionally from its Dashboard. The plugin will be active, but not doing anything helpful. Visit the site and check if it works now. If there are white pages or other issues, there might be something else wrong that I’m not familiar with.
  9. Turn on W3 TC Page Caching, and try the site again. You may need to clear the page cache as well.

If Page Caching works now that you’ve uninstalled, reinstalled, and reconfigured W3 TC, you can continue by turning on other parts of W3 TC and testing they work. If not, use the W3 Total Cache Support boards to find a solution to your problem.

Safari, 3rd party cookies, and Facebook apps

The default settings for the Safari web browser block 3rd party cookies. This means that web apps hosted on https://mysite.com will not be able to set cookies when displayed within an iframe in a Canvas App (https://apps.facebook.com/myappdomain) or Page Tab. Problems ensue when the web app relies on cookies for sessions and CSRF checks.

Here is an ugly but functional fix. When the page first loads within the frame, check if any cookies are set. For example, a Laravel-based site might use this PHP code in a before filter:

if (count($_COOKIE) === 0) {
     return '<script>top.location = "https://mysite.com/cookiefix"</script>';
}

This will redirect the user out of Facebook and into a page on mysite.com where the web application can set cookies as the 1st party instead of the 3rd party. With Laravel, the session token and payload cookies are set automatically at this time. Then page writes out the following code in the HTTP response to redirect the user back into the Facebook app:

'<script>top.location = "https://apps.facebook.com/myappdomain"</script>';

With a broadband connection and healthy web server, the redirect happens so fast on a speedy site that most people do not notice the URL changing to from a Facebook URL to an external URL and back. This time when the check for cookies returns a number > 0, the app continues as usual. In my experience, the app is able to make changes to the cookies now as well.

Caveats

This quick fix does not cover situations where the user is landing on a page deep in your app. If user visits https://apps.facebook.com/myappdomain/internalpage, they will be redirected back to https://apps.facebook.com/myappdomain after the cookie is set. This can be fixed by saving the full URL the user landed arrived at without cookies, and returning the user to that URL instead of https://apps.facebook.com/myappdomain.

This fix will also put users who have intentionally disabled cookies into an infinite loop of redirects between the Facebook app and /cookieredirect page. Whether or not this is an issue for your app’s audience is up to you. A way to prevent the loop may be to store the number of times the user has gone through the redirect, and send the user to a page that doesn’t redirect them again if they have gone through it more than once without the cookie being successfully set.

An Alternative

An alternative to the redirect is to briefly open a popup window containing your own site, and use a similar behaviour as the /cookiefix URL to set cookies, and then close the popup. Use an interaction like a click to open this popup, otherwise Safari may block it.

Related links:
Discussion on Stack Overflow
Another discussion on Stack Overflow

Laravel 3 Admin Area bundles

Opinions based on spending less than an hour with each bundle:

Orchestra

Orchestra screenshot

Orchestra gives you more framework to build on top of. It has its own login system and generates a few, mostly empty pages, and then creating views, models, and more for rest of your administration area is up to you. There is lots of documentation available which requires the Bundocs bundle to view. It is definitely easier to install this bundle on a fresh install of Laravel. For example, its user’s table may exist with the one in your database already.

Adminify

Adminify Screenshot

Adminify uses your Laravel models to generate a simple visualization of your tables and lets your perform simple CRUD operations on the rows. It can also view logs in your storage/logs directory, and gives you a text area to enter and execute MySQL queries from.

Administrator

Administrator lets you perform CRUD operations on the rows in your database. It uses your app’s eloquent models and additional configuration files. The configuration files make it quite powerful since it lets you set up filtering, specify different form elements for editing different table columns, create new getters, and manage some relationships between tables. Documentation is here.

 

Laravel Asset Compressor Bundle and filemtime() stat error

This appears to only be a problem with Windows. If you see an error like this while using the asset compressor:

filemtime() [function.filemtime]: stat failed for [some file path]
Location:
 
E:\www\yourapplication\laravel\file.php on line 136

know that the code probably works on Linux and OS X, but not on your own Windows machine.

Why?

OS X and Linux use / as a directory separator. Windows uses \. PHP is tolerant of this, but the Google Closure Compiler that does the heavy lifting for this bundle is not. It doesn’t work with Windows paths containing \, like the ones the Asset Compressor has generated for it to use. If you get the command that the asset compressor generates (look for exec("$java $jar $scripts_to_minify $out_script");), replace all the \ with /, and run that line through the command line, you may find it works!

The Quick Solution

Set the enabled property in the bundle’s config file to something like this to disable the compressor on Windows, and enable it on other operating systems with the right directory separator:

    'enabled' => DIRECTORY_SEPARATOR == '/',

If your live server uses Linux, the compiler will compress your assets the first time someone visits your site. This can add several additional seconds to any page load that triggers the compiler.

The Smarter Solution

Fork it, fix it, pull request it.

Splitting PHP class files for PSR-0 compliance

Have a ton of classes in one file that need to be split up? Check out Matthew Turland’s class splitter script.

Put it in a folder somewhere in your computer, open up a terminal in that location, and then use it like this:

php php-class-splitter.php /path/to/file/with/lotsofclasses.php /path/to/new/class/directory

It is likely that you will end up putting your new split classes in the same location as the original file. If you are going for PSR0 compliance, you will need to add a namespace to each class after.