Fast Symfony2 served on a stable Xubuntu

Ingredients:

Introduction:

This recipe is for people who want to install symfony2 quickly with all dependencies, without having to read the excellent documentation that can be found here:

  1. The quick tour
  2. The book
  3. The cookbook

This recipe can be used by linux users or by windows users that run linux in a virtual machine.

This will get you:

  • A working Symfony2 installation
  • Pretty URL’s (using mod_rewrite)
  • Optimized execution speed (using php5-apc)
  • Configured timezone settings and internationalization support (using php5-intl)
  • Run as a specific user and not as www-data (using apache2-mpm-itk)
  • Created a database and installed a database management tool (phpmyadmin).

The instructions are meant for a development platform. If you use these instructions on a production platform the least you should do is choose a more secure password. NB: You also might want to run Ubuntu Server instead of Xubuntu in such a case.

This will NOT get you:

  • A debugging environment (using XDebug)
  • An PHP IDE with debugging integration (Eclipse PDT)

For serious web development you might want the above: a full-featured IDE and a step-by-step debugger. This will be discussed in another post.

Instructions:

The lines below with a “-” are actions and the lines with a “$” are commands:


- Download and install Virtualbox
- Download xubuntu iso
- Configure a new Virtual Machine with:
  OS set to: Linux/Ubuntu 64
  4 cores (assuming you have 8 cores)
  2048 MB RAM (assuming you have at least 4096)
  20 GB VDI dynamic disk
- Select the Xubuntu iso file in the first run wizard
- Install Xubuntu by following the steps
- Install additional drivers (guest additions) and reboot
- Open up a web browser
- Go to http://symfony.com/download and click download
- Save "Symfony_Standard_Vendors_2.x.xx.tgz" and unpack it
- Move the "Symfony" folder to your home directory and rename it to "public_html"
$ sudo apt-get install lamp-server^ php-apc php5-intl php5-sqlite phpmyadmin
 choose a mysql root password
 choose to configure phpmyadmin for apache2
$ sudo apt-get install apache2-mpm-itk
$ sudo a2enmod rewrite
$ sudo nano /etc/php5/apache2/php.ini
 NB: use Ctrl-W Ctrl-R to search and replace
 search: ;date.timezone =
 replace: date.timezone = Europe/Amsterdam
 NB: enter your local timezone if you are not in Europe/Amsterdam
 (see http://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
 search: short_open_tag = On
 replace: short_open_tag = Off
$ pwd
 NB: note down the output, as this is the path of your home folder (in my case: /home/maurits)
$ id -un
 NB: note down the output, as this is your user name (in my case: maurits)
$ id -gn
 NB: note down the output, as this is your group name (in my case: maurits)
$ sudo nano /etc/apache2/sites-enabled/000-default
 NB: use Ctrl-W Ctrl-R to search and replace
 search: /var/www
 replace: {YOUR_HOME_FOLDER}/public_html/web
 NB: you have to enter the home folder path you found from the pwd command instead of {YOUR_HOME_FOLDER}
$ sudo nano /etc/apache2/sites-enabled/000-default
 NB: add the following line under the DocumentRoot
 AssignUserId {YOUR_USER_NAME} {YOUR_GROUP_NAME}
 NB: you have to enter your user name and group name instead of {YOUR_USER_NAME} {YOUR_GROUP_NAME}
$ sudo service apache2 restart
- Open web browser and go to http://127.0.0.1/phpmyadmin
- Login with root user and chosen root password
- Click "Privileges"
- Click "Add a new User"
- Enter/choose:
 username: symfony
 host: localhost
 password: symfony
 retype: symfony
- Select "Create database with same name and grant all privileges"
- Click "Create User"
- Open a web browser and go to http://127.0.0.1/config.php
- verify that symfony does not complain about anything
- click "Configure your Symfony Application online"
- Enter/choose:
 Driver: MySQL (PDO)
 Host: localhost
 Name: symfony
 User: symfony
 Password: symfony
 Password again: symfony
- Let symfony generate a "Global Secret"
- Choose "Next Step"
- Symfony should say: Your distribution is configured!
- Click "Go to the Welcome page"
- Click "Run the Demo"
- Play around with the demo to see that everything works

Bonus instructions:

Some nice tips on how to customize your Xubuntu:

That is it for today, have fun!

Share

Adminer: good alternative for phpMyAdmin

NB: This is an outdated post. New Ubuntu versions use Apache 2.4 instead of Apache 2.2. You can find updated instructions that apply to Ubuntu 14.04 in a new post!

It is not a secret that do a lot of Symfony programming at LeaseWeb. Most of the projects are PHP based and use MySQL as a database. This posts describes a gem that many experienced PHP programmers have not yet heard of.

During software development you often need a tool to quickly search the database, add a column or change a value. Most PHP developers know that phpMyAdmin is a good tool that can provide you with easy access, but only a few know that there is really good alternative for this tool called “Adminer“.

In my opinion Adminer has the advantage that it has a better user experience, but this is not the only advantage it has (or may have). According to the Adminer website there is a long list of reasons why Adminer is better than phpMyAdmin.

If you are really fond of Adminer, you might want to install it in the same way phpMyAdmin is installed. Since it is not yet part of the standard Ubuntu repositories you might want to install it manually. Here are the commands you need for installation (on a Debian based system):

$ sudo su
# mkdir /usr/share/adminer
# cd /usr/share/adminer
# wget "http://www.adminer.org/latest.php"
# ln -s latest.php adminer.php
# echo "Alias /adminer.php /usr/share/adminer/adminer.php" > /etc/apache2/conf.d/adminer.conf
# service apache2 restart
# exit
$

Once it is installed and you want to update it (it wil indicate when a new version is available):

$ sudo su
# cd /usr/share/adminer
# wget "http://www.adminer.org/latest.php"
# exit
$

To uninstall it:

$ sudo su
# rm -R /usr/share/adminer
# rm /etc/apache2/conf.d/adminer.conf
# service apache2 restart
# exit
$

Try it out and let me know whether you like it or not.

Share

Migration to Symfony2 continued

On December 21, 2011 Stefan Koopmanschap wrote an excellent article on this blog titled “Painless (well, less painful) migration to Symfony2.” In his article he explained the advantages of doing a gradual migration. The technical solution that he proposed to make this possible was to “…wrap your old application into your Symfony2 application.” He even provided us the tool (The IngewikkeldWrapperBundle code) to do so.

We were very much inspired by his passionate elucidation and we were fully convinced of the urge to start migrating to Symfony2 as soon as possible. However, he also provided us with a “A word of caution” about 2 things: performance and authentication/authorization. This might get some people worried, but not us: it challenged us to find a solution for those two open issues.

1. Performance

As Stefan Koopmanschap explains, in his solution you “…use two frameworks for all your legacy pages” and “…two frameworks add more overhead than one.” Since our Symfony1 application (the LeaseWeb Self Service Center) is not the fastest you’ve ever seen, some customers are even complaining about it’s speed, this got us a little worried. Losing performance was not really an option, so we had to find a solution.

Symfony 1 & 2 both use a Front Controller architecture (one file handling all requests) we were just looking for seperating traffic between the two front controllers. Stefan proposed to do so using Symfony 2 routing and make it use a fall-back route to handle your legacy URLs. We hereby propose to do it using a .htaccess rewrite. This has virtually no overhead, because every Symfony request gets rewritten by mod_rewrite anyway.

2. Authentication/authorization

He also wrote: “Another issue is sessions.” Further clarifying the problem by stating: “If your application works with authentication and authorization, you’ll now have to work with two different systems that have a different approach to authentication and authorization”. Since our application requires both authentication and authorization we had to come up with a solution here. We decided to move the authentication (login page) to Symfony2 and make Symfony1 “trust” this authentication done by “Symfony2”.

To realize this solution we had to enable Symfony1 to “see” and “understand” the Symfony2 session. First we made sure that both applications use the same name by setting the Symfony2 “framework_session_name” setting in “app/config/config.yml” to “symfony”. Then we reverse engineered the Symfony2 session storage and found that it serializes some PHP object into it. To be able to unserialize those objects we had to register an autoload function in Symfony1 using “spl_autoload_register”

Finally, instructions

To solve the performance problem we installed Symfony2 in the “sf2” directory inside the Symfony1 project (next to “apps”) and we started by changing the lines in our “web/.htaccess” file from:

# redirect to front controller
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]

And added these lines above it:

# redirect to new symfony2 front controller
RewriteCond %{REQUEST_FILENAME} !-f
# but only if URL matches one from this list:
RewriteCond %{REQUEST_URI} ^/users/login
# end of list
RewriteRule ^(.*)$ sf2/web/$1 [QSA,L]

To support the Symfony2 authentication and authorization in Symfony1 we created a “Symfony2AuthenticationFilter” class. This filter can be loaded by putting it under “lib/filter” folder in your Symfony1 project and add the following lines in “apps/ssc/config/filters.yml”:

symfony2AuthenticationFilter:
    class: Symfony2AuthenticationFilter

For configuration of the filter we added a few new application settings to “/apps/ssc/config/app.yml”:

all:
    symfony2:
        paths: ['sf2/vendor/symfony/src', 'sf2/src', 'sf2/vendor/bundles', 'sf2/vendor/doctrine-common/lib']
        attribute: '_security_main'

This path setting shows that Symfony2 is located in the “sf2” sub-directory of the Symfony1 project. The attribute reflects the name of the Symfony2 firewall. The code of the Symfony2AuthenticationFilter is this:

function symfony2_autoload ($pClassName)
{
  $sf2Paths = sfConfig::get('app_symfony2_paths');

  foreach ($sf2Paths as $path)
  {
    $path = sfConfig::get('sf_root_dir') . DIRECTORY_SEPARATOR . $path;
    $file = $path . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR ,$pClassName ) . ".php";

    if (file_exists($file))
    {
      include($file);
      break;
    }
  }
}

spl_autoload_register("symfony2_autoload");

use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Role\Role;

class Symfony2AuthenticationFilter extends sfFilter
{
  public function execute($filterChain)
  { // get session data
    $sessionData = null;
    $symfony2Attribute = sfConfig::get('app_symfony2_attribute');
    if (isset($_SESSION['_symfony2']['attributes'][$symfony2Attribute]))
    { $sessionData = unserialize($_SESSION['_symfony2']['attributes'][$symfony2Attribute]);
    }
    // get sf1 username
    if (!$this->getContext()->getUser()->isAuthenticated()) $sf1UserName = false;
    else $sf1UserName = $this->getContext()->getUser()->getUserName();
    // get sf2 username
    if (!$sessionData) $sf2UserName = false;
    else $sf2UserName = $sessionData->getUser()->getUserName();
    // if usernames do not match
    if ($sf1UserName!=$sf2UserName)
    { if ($sf2UserName) // if symfony2 is signed in
      { // signin to symfony1
        $this->getContext()->getUser()->setUserName($sf2UserName);
        $this->getContext()->getUser()->setAuthenticated(true);
        $this->getContext()->getUser()->clearCredentials();
      }
      else // if symfony2 is not signed in
      { // signout from symfony1
        $this->getContext()->getUser()->setUserName(false);
        $this->getContext()->getUser()->setAuthenticated(false);
        $this->getContext()->getUser()->clearCredentials();
        // redirect to current page
        $path = $this->getContext()->getRequest()->getPathInfo();
        $this->getContext()->getController()->redirect($path);
      }
    }
    // and execute next filter
    $filterChain->execute();
  }
}

Update (Jan 22, 2013): You can now find the bundle we created for this on the LeaseWeb Labs on GitHub account and Packagist:

Share

Subversion revision information in the Symfony debug toolbar

At LeaseWeb we are very serious about software development and that is why we hire software testers. These people do risk analysis, think of new test cases, execute them and organize them in test suites using TestLink. We also use PHP and are great fans of the Symfony MVC framework. Being serious about Agile software development means that we do TDD using unit tests and functional test. For this we use the Symfony build-in unit and functional tests. We also use Selenium for creating browser-driven test suites. The advantages of Selenium over the functional tests in Symfony are cross browser testing and JavaScript testing.

We have this product called “SSC” (this is the hosting control panel for the customers) and we test it thoroughly and often. For this product we have a DTAP environment to do testing and acceptance testing. Within the tests of this product the testers encountered a challenge: How do the testers know what revision of our application they are testing and what branch they are testing? This is especially a problem with acceptance testing where interactions between various systems are tested. Because the testers do not have command line access on the Linux machines that run the acceptance environment they cannot simply issue the “svn info” command like developers can.

To solve this problem we wrote a Symfony plugin called “lwTestingInformationPlugin”. It shows the “svn info” command output in a popup that is accessible from the Symfony debug toolbar. Even though it was extremely easy to write, it could be very useful. We made it available for download, so if you think it might be useful for your project as well, please try it and send us your feedback!

Click here to download the plugin (lwTestingInformationPlugin.tar)

TestLink = http://www.teamst.org/
Symfony = http://www.symfony-project.org
Selenium = http://seleniumhq.org/
Subversion = http://subversion.apache.org

Share