Working with the PHP Laravel Framework

laravel-logo-bigI’ve worked with Laravel for about a year and half now and it’s the third PHP framework I’ve  learned so far (the others are Code Iginiter (2 projects) and Zend (1 project).

So, what’s to like about Laravel and what are the niggles?

As a coder, there’s lots to like about Laravel. The code is clean and well written. It follows all the best Object Oriented practices, uses patterns and is generally easy to read.

Niggles: It’s definitely the pet project of the guy who created it, Jeffrey Way. That means whatever he decides goes. Expect some quite quirky bits of code.

The main niggle, which also happened with Zend, is the project structure has changed in a way to make an upgrade really complicated. With Zend, the upgrade from Zend 1.2 to Zend 2.0 meant that you had to substantially re-write your project. This was a major annoyance on lots of levels. It increased the cost to the client and it also meant I had to learn Zend again.

Having written a fair amount of code in Laravel 4.2 for a client, I took a look to see if I could easily update it to Laravel 5.1 Answer, not at all. I took nearly a whole day to experiment on this and only managed to get some basic pages up.

I’ve written a simple Laravel 5 project to demo my code. There wasn’t that much to relearn once I had got used to the project structure. The major thing I can see is the addition of middleware.

Laravel is still a great framework to work with and I look forward to doing further work on it.

Installing Redis to use as Laravel 4 cache

The following commands will install redis on a server running CentOS 6.4.

First, install the epel repo

sudo rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm 

Next, install the remi repo

sudo rpm -Uvh http://rpms.famillecollet cialis bestellen ohne rezept.com/enterprise/remi-release-6.rpm 

Now, you should be able to install redis using the yum package manager.

yum install redis -y http://codybonney.com/installing-redis-on-centos-6-4/
http://codybonney.com/installing-redis-on-centos-6-4/

mkdir /usr/share/redis
cd /usr/share/redis
git clone https://github.com/ErikDubbelboer/phpRedisAdmin.git
cd phpRedisAdmin
git clone https://github.com/nrk/predis.git vendor
ln -s /usr/share/redis/phpRedisAdmin /vagrant/public/redis

in local/config/database.php

'redis' => array(
'cluster' => false,
'default' => array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 0,
),
)
'driver' => 'redis',
  • Find out the name of service’s script from /etc/init.d/ directory e.g. redis
  • Add it to chkconfig
    sudo /sbin/chkconfig --add redis
  • Make sure it is in the chkconfig.
    sudo /sbin/chkconfig --list redis
  • Set it to autostart
    sudo /sbin/chkconfig redis on

					

Zend Framework: Uploading and resizing an image

I’ve needed to upload and resize an image on a Zend Framework. Not especially difficult but it does take a little setting up.

First, I need to set up some upload locations for the final files. This was in my application.ini file like this:

paths.images.thumbs=APPLICATION_PATH "/../public/photos/thumbs"
paths.images.web=APPLICATION_PATH "/../public/photos/web"
paths.images.original=APPLICATION_PATH "/../public/photos/original"

Then I needed to set up the form

class Application_Form_Photos extends Zend_Form
{

    public function init()
    {
        $decorator = new Decorators_Inputs();

        // Add a name element
        $this->addElement('text', 'name', array(
            'label' => 'Photo Name:',
            'required' => true,
            'style' => 'width:200px;',
            'filters' => array('StringTrim'),
            'validators' => array(
                'NotEmpty'
            ),
            'decorators' => array($decorator)
        ));

        $photo = new Zend_Form_Element_File('photo');
        $photo->setLabel('Photo (jpg only)')
            ->setDestination(Zend_Registry::get('config')->paths->images->original);
        // ensure only one file
        $photo->addValidator('Count', false, 1);
        // max 10MB
        $photo->addValidator('Size', false, 10097152)
            ->setMaxFileSize(10097152);
        // only JPEG, PNG, or GIF
        $photo->addValidator('Extension', false, 'jpg,png,gif');
        $photo->setValueDisabled(true);
        $photo->setDecorators(array('File', new Decorators_Files()));
        $this->addElement($photo, 'photo');

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setValue('Upload');
        $submit->setName('Upload');
        $submit->setDecorators(array(new Decorators_Submit()));
        $this->addElement($submit);

    }

}

The Decorators_Files class is a decorator (See here for how to set that up)

class Decorators_Files extends Zend_Form_Decorator_Abstract
{
    protected $_format = '
    
%s 

'; public function render($content) { $element = $this->getElement(); $name = htmlentities($element->getFullyQualifiedName()); $label = htmlentities($element->getLabel()); $id = htmlentities($element->getId()); $value = htmlentities($element->getValue()); $style = htmlentities($element->getAttrib('style')); $class = htmlentities($element->getAttrib('class')); $error = ""; $errors = $element->getErrors(); if($errors){ $error = 'Please enter a value'; $class = 'error'; } $markup = sprintf($this->_format, $name, $label, $id, $name, $value,$class, $style,$error); return $markup; } }

Now, I need the code to handle the upload

/**
     * Handle the image upload
     * @param Application_Form_Photos $form, the form that contains the image
     * @param Zend_Controller_Request_Abstract $request, the request
     * @param Application_Model_MediaMapper $media_mapper, the Model class which handles saving the file details
     */
    private function handle_image_upload(Application_Form_Photos $form, Zend_Controller_Request_Abstract $request, Application_Model_MediaMapper $media_mapper)
    {
       //Create an entry in the DB
        $extension = pathinfo($form->getElement('photo')->getValue(), PATHINFO_EXTENSION);
        $media_model = new Application_Model_Media();
        $media_model->setName($request->getParam('name'))
            ->setThumb('')
            ->setWeb('')
            ->setOriginal('');
        $media_mapper->save($media_model);

       //Rename the file
        $form->getElement('photo')->addFilter('Rename', array(
            'target' => $media_model->getId() . '.' . $extension,
            'overwrite' => true
        ));

        //Add the filter to resize to web image
        $form->getElement('photo')->addFilter(new Filters_File_Resize(array(
            'width' => 800,
            'height' => 600,
            'keepRatio' => true,
            'directory' => Zend_Registry::get('config')->paths->images->web
        )));

        //Add the filter to resize to thumb, note it can't be the same filter as above or the web image action will fail
        $form->getElement('photo')->addFilter(new Filters_File_Thumb(array(
            'width' => 200,
            'height' => 133,
            'keepRatio' => false,
            'directory' => Zend_Registry::get('config')->paths->images->thumbs
        )));

        //Upload the photo
        if ($form->getElement('photo')->receive()) {
            $media_model->setThumb('photos/thumbs/' . $media_model->getId() . '.' . $extension);
            $media_model->setWeb('photos/web/' . $media_model->getId() . '.' . $extension);
            $media_model->setOriginal('photos/original/' . $media_model->getId() . '.' . $extension);
            $media_mapper->save($media_model);
        }
    }

Finally, I need some filters that will handle the image resize.

I initially used the filter written by Stefan Koch. See
http://eliteinformatiker.de/2011/09/02/thumbnails-upload-and-resize-images-with-zend_form_element_file/

Moodle unit testing through simple test

I’ve used the simpletest PHP unit test framework and as Moodle uses the simpletest, I thought that setting up unit tests and running them would be straightforward. As with a lot of things Moodle, there are various gotchas which are not documented and it was only by searching through the source code that I was able to get them running.

I set up a unit test class and ran it through Site Administration -> Development -> Unit Tests. The first thing that went wrong was it complained that unittestprefix had not been set. A search through the source revealed that this variable refers the prefix you need to use on the test tables in the Moodle database. In my config.php, the setting was

//Unit testing. 
//This refers to the prefix for the test tables in the database
$CFG->unittestprefix = 'testing_';

The next problem was that Moodle couldn’t find my unit tests. It took a bit of digging but I found that I had prefix the name the php file with the unit test with ‘test_’. After that, my tests worked OK.

if (!defined('MOODLE_INTERNAL')) {
   ///  It must be included from a Moodle page
    die('Direct access to this script is forbidden.');    
}

global $DB, $CFG;

class test_block_rollover_clone_test extends UnitTestCase
{
    public function testInitialTesting()
    {
        $this->assertFalse(true);
    }

}

This resulted in the following:

Moodle form validation tips

There are a number of gotchas with Moodle Quick Form and it’s only through trial and error that I’ve discovered how they work.

The first is that when you implement any sort of validation, the form will only return data when the validation passes.

The second is that you don’t call any custom validation directly, you need to check that is_validated() is returning true.

class block_rollover_form extends moodleform
{

    function definition()
    {
        $form = $this->_form;
        //add form definitions here
        $form->addElement('date_selector', 'livedate', get_string('livedate', 'block_rollover'), array(
            'startyear' => 2012,
            'stopyear' => 2020
        ));

        $form->addElement('date_selector', 'archivedate', get_string('archivedate', 'block_rollover'), array(
            'startyear' => 2012,
            'stopyear' => 2020
        ));
        
    }

    /**
     * Custom validation to check that the start date and archive date are in future
     *
     * @param array $data
     * @param array $files
     * @return array
     */
    function validation($data, $files)
    {
        $errors = parent::validation($data, $files);
        $now = time();
        $live_date = $data['livedate'];
        $archive_date = $data['archivedate'];
        if ($live_date <= $now) {
            $errors['livedate'] = 'Please ensure that the start date for the new course is after today';
        }

        if ($archive_date <= $now) {
            $errors['archivedate'] = 'Please ensure that the archive date for the old course is after today';
        }
        return $errors;
    }


    /**
     * This function handles the form submission
     */
    public function handle()
    {
        //Do nothing if submitted or cancelled.
        if (!$this->is_submitted() || $this->is_cancelled()) {
            return;
        }

        //If the validation fails, it will print out the error messages on the form
        //note that $this->get_data() will return empty until the validation passes.
        if (!$this->is_validated()) {
            return;
        }

        //Form has been submitted and it validates, now we can get the data in the form
        $data = $this->get_data();

    }
}

Tomcat6 on Ubuntu, heap size and perm gen size

I’ve been having some trouble with a server running Tomcat6 on Ubuntu with a Spring/Hibernate set up. It also does some image resizing via Image Magick.

The problems were that the perm gen size for the Hibernate objects was too small and it ended up running out of memory.

No problem, in Ubuntu, the fix was going /etc/default/tomcat6 and editing the JAVA_OPTS variable.

I started off with

JAVA_OPTS="-XX:MaxPermSize=512m"

That solved the Hibernate problem but I was soon getting out of memory errors for image uploads. I had to add this into the file

JAVA_OPTS="-XX:MaxPermSize=512m -Xms256m -Xmx512m"

Running this gave me some useful feedback about the memory that Java was using.

jmap -heap <pid>

There are some useful resources out there for anyone interested in reading more

http://diegobenna.blogspot.co.uk/2011/02/how-to-increase-heap-size-in-tomcat-6.html

http://www.yaronco.com/tomcat6-and-java-heap-size/

PHP upload limits

Every now and then I need to re-set the PHP upload limits in the PHP ini file.

The obvious setting is

upload_max_filesize = 1024M

The less obvious settings are:

file_uploads = On
max_file_uploads=20
post_max_size=1024M

I’ve found it easy to forget to change post_max_size which need to be the same or greater than the upload_max_filesize.

Zend Framework, styling and decorating a form

Although extending Zend_Form produces a really easy form, the layout that results is less than perfect. This is where subclassing Zend_Form_Decorator_Abstract comes in. Here’s the implementation for a text field.

class Decorators_Inputs extends Zend_Form_Decorator_Abstract
{
    protected $_format = '
    <div class="element">
        <div class="label_col"
            <label for="%s">%s</label>
        </div>
         <div class="input_col">
            <input id="%s" name="%s" type="text" value="%s" class="%s" style="%s"/>
         </div>
         <div class="error_col">
            %s 
         </div>
     </div>
     <br clear="left" />
     ';

    public function render($content)
    {
        $element = $this->getElement();
        $name    = htmlentities($element->getFullyQualifiedName());
        $label   = htmlentities($element->getLabel());
        $id      = htmlentities($element->getId());
        $value   = htmlentities($element->getValue());
        $style   = htmlentities($element->getAttrib('style'));

        $class = "";
        $error = "";
        $errors = $element->getErrors();
        if($errors){
            $error = 'Please enter a value';
            $class = 'error';
        }

        $markup  = sprintf($this->_format, $name, $label, $id, $name, $value,$class, $style,$error);
        return $markup;
    }
}

Zend framework, sending an email

Sending an email in the Zend Framework turns out to be reasonably easy. This example uses the contact form for a web site

public function contactAction()
    {
        $request = $this->getRequest();
        $form = new Application_Form_Contact();

       //Check if form has been submitted
        if ($this->getRequest()->isPost()) {
            if ($form->isValid($request->getPost())
              && $this->send($this->getRequest())) {

                $this->_forward('thanks');
            }
        }

        $this->view->form = $form;
    }

    private function send(Zend_Controller_Request_Abstract $request)
    {

        $session = new Zend_Session_Namespace();

        if (isset($session->numberOfPageRequests)) {
            $session->numberOfPageRequests++;
        } else {
            $session->numberOfPageRequests = 1;
        }

        //pretend mail has been sent for people repeatedly hitting refresh
        if ($session->numberOfPageRequests > 2) {
            return true;
        }

        $email_mapper = new Application_Model_EmailMapper();
        //Get the email text from the database.
        $email = $email_mapper->getEmailByName('ContactEmail', $request);

        $mail = new Zend_Mail();
        $mail->setBodyText($email->getText());
        $mail->setBodyHtml($email->getHtml());
        $mail->setFrom('somebody@example.com', 'Some Sender');
        $mail->addTo('somebody_else@example.com', 'Some Recipient');
        $mail->setSubject('TestSubject');
        $mail->send();

        return true;

    }

This is the result of sending the email

I’m using Toolheap’s Test email SMTP server to send test emails during development. It’s proven to be a very useful tool for verifying designs and content of the emails before they are put live.

Exporting large databases with MySQL dump

I had to export a 7GB database recently using mysqldump.

I started out with the usual export call

mysqldump -u admin -p sugar > sugar_28_may.sql

However, the users complained that they couldn’t access the system while it was being exported.

The culprit was mysqldump’s habit of locking tables to export them. The solution was this

mysqldump -u admin -p --lock-tables=false sugar > sugar_28_may.sql

The other problem I got was this message

mysqldump: Error 2020: Got packet bigger than 'max_allowed_packet' bytes when dumping table `emails_text` at row: 84538

That message meant that I had to wait 15 minutes while the backup went up to 2GB and then the error kicked in.

I checked the max_allowed_packet by running

mysqldump --help
---
max_allowed_packet                25165824

It turned out the server had a 24MB packet limit. I increased it to 1GB.

mysqldump -u admin -p --max_allowed_packet=1073741824 --lock-tables=false sugar >sugar_28may12.sql

Note that the –max_allowed_packet option on MySQL 5.0xx seems to be max-allowed-packet on MySQL 5.5x. Running mysqldump –help will sort out the correct syntax to use.