Friday, February 11, 2011

How to free Symfony2 from the sandbox tutorial (with some help from Git)

Note

It's possible that this tutorial works no more, because of changes made to Symfony2 after release PR6.

----
The Symfony2 sandbox application is a great base to start digging into writing Symfony2 apps. When I tried to get my first overview of how to build an app with Symfony2 I just downloaded the symfony2-sandbox from https://github.com/symfony/symfony-sandbox/archives/master and extracted it somewhere onto my development machine, lets assume our folder is sven@dev:~/sandbox$

Prerequisites:
  1. Git installed, check

Hello Git, how are you :)

Create a new local git repository with git init:
(No need now for a remote repository)
sven@dev:~/sandbox$ git init

Goodbye vendors, Hello vendors!

SVN had svn-externals, Git has submodules, so we can make our lives a little bit easier and make use of git submodules to include all vendors as independent git repositories, called submodules. To achieve this we have to execute the command git submodule add <repository-url> <checkout-directory>.
Just execute the following list line by line on your command line:

sven@dev:~/sandbox$
git submodule add git://github.com/symfony/symfony.git           vendor/symfony
git submodule add git://github.com/fabpot/Twig.git               vendor/twig
git submodule add git://github.com/fabpot/Twig-extensions.git    vendor/twig-extensions
git submodule add git://github.com/doctrine/common.git           vendor/doctrine-common
git submodule add git://github.com/doctrine/doctrine2.git        vendor/doctrine
git submodule add git://github.com/doctrine/dbal.git             vendor/doctrine-dbal
git submodule add git://github.com/doctrine/data-fixtures.git    vendor/doctrine-data-fixtures
git submodule add git://github.com/doctrine/migrations.git       vendor/doctrine-migrations
git submodule add git://github.com/doctrine/mongodb.git          vendor/doctrine-mongodb
git submodule add git://github.com/doctrine/mongodb-odm.git      vendor/doctrine-mongodb-odm
git submodule add git://github.com/swiftmailer/swiftmailer.git   vendor/swiftmailer
git submodule add git://github.com/zendframework/zf2.git         vendor/zend

Now you have included every dependency to a vendor as a seperate git repository, now just call git submodule update --init to update everything.
sven@dev:~/sandbox$ git submodule update --init

You like it slow?

Symfony2 has a bootstrap file under ~/sandbox/vendor/symfony/src/Symfony/Component/HttpKernel/bootstrap.php which includes the whole framework for maximum performance, so it never loads the real classes under vendor/symfony/src/Symfony, for debugging and testing it can be useful to include the real classes and not the bootstrap file, for this you need to change two files:

1: sven@dev:~/sandbox/app/bootstrap.php

Comment out the line:
require_once __DIR__.'/../vendor/symfony/src/Symfony/Component/HttpKernel/bootstrap.php';

2: sven@dev:~/sandbox/app/autoload.php

Add at the beginning of the file the following line:
require_once(__DIR__ . '/../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php');

More to delete, just to be up to date

Delete the contents of sven@dev:~/sandbox/bin$ and create a new folder called ant-tasks and a new file called update-vendors.xml. Paste the following content into the file:

<project name="update-vendors" basedir="./../../">
    
    <target name="update-doctrine">
        <exec dir="${basedir}/vendor/doctrine" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-common">
        <exec dir="${basedir}/vendor/doctrine-common" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-dbal">
        <exec dir="${basedir}/vendor/doctrine-dbal" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-data-fixtures">
        <exec dir="${basedir}/vendor/doctrine-data-fixtures" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-migrations">
        <exec dir="${basedir}/vendor/doctrine-migrations" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-mongodb">
        <exec dir="${basedir}/vendor/doctrine-mongodb" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-doctrine-mongodb-odm">
        <exec dir="${basedir}/vendor/doctrine-mongodb-odm" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-symfony">
        <exec dir="${basedir}/vendor/symfony" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-twig">
        <exec dir="${basedir}/vendor/twig" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-twig-extensions">
        <exec dir="${basedir}/vendor/twig-extensions" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-swiftmailer">
        <exec dir="${basedir}/vendor/swiftmailer" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
    
    <target name="update-zend">
        <exec dir="${basedir}/vendor/zend" executable="git" failonerror="true">
            <arg line="pull" />
        </exec>
    </target>
</project> 

This file is an ant tasks file, you can execute the tasks described in there with ant to update your vendors.

Done

Now make your sandbox folder accessible by your webserver and navigate to the web folder and open the app.php or app_dev.php file in your browser, Symfony2 should running without any problems.

Another tip about git submodules

Sometimes it's a bit dangerous to get the most bleeding edge checkouts from the vendors, so you can update the sources/submodules and after that, navigate into the checkout folder and switch to a tag with git checkout <tag_name>.

Wednesday, February 9, 2011

Form naming

Here is a short note, never forget to give your Form a name when calling Form::create:
# inside your controller

    public function formAction()
    {
        $context = $this->get('form.context');
        $request = $this->get('request');

        $form = MyForm::create($context, 'myform'); // 2nd parameter is the name of the form
        $form->bind($request);
  
        
        return $this->render('MyBundle:Index:form.html.twig', array(
            'form' => $form
        ));
    }

If you forget to pass a name the automatic token check will report everytime an error after submission.

Translate form labels inside Twig templates

In this exmaple I assume you've a configured transaltion service up and running. This tip works when you're iterate over loops, like in this example and/or when you print everything out by hand ({{ form_label(form.my_field, label) }}).
{% block someblock %}
    <form action="#" method="post" {{ form_enctype(form) }}>
        {{ form_errors(form) }}

        {% for field in form %}
            {% if not field.ishidden %}
                <div>
                    <!-- translates the field name (stored in key) and saves it in the variable label -->
                    {% set label %}{% trans field.key %}{% endset %}   
                    
                    <!-- passes the translated name as the second parameter for your own label -->
                    {{ form_label(field, label) }}

                    {{ form_errors(field) }}
                    {{ form_field(field) }}
                </div>
            {% endif %}
        {% endfor %}

        {{ form_hidden(form) }}
        <input type="submit" />
    </form>
{% endblock %}

Dependency injection for Controllers

To get dependency injection working properbly for controllers in your Symfony2 bundles you have to take care of several things, let's start with an example service configuration:

Service config
# src/MyBundle/Resources/config/config.yml 
services: 
  view: 
    class: "MyBundle\View"
    shared: true
    arguments: 
      container: "@service_container"
        
  my_bundle_controller_index: 
    class:  "MyBundle\Controller\IndexController"
    shared: true
    arguments: 
      view: "@view"
    calls:
      - [ setContainer, ["@service_container"] ]

The name of the controller service is my_controller_bundle_index.
By default your routing.(yml|xml|php) file would look like this:
# src/MyBundle/Resources/config/routing.yml 
my_bundle_route:  
  pattern: my/bundle/route  
  defaults: 
    _controller: MyBundle:IndexController:indexAction

Your service configuration is properly loaded via your dependency injection extension (src/MyBundle/DependencyExtension/MyExtension.php) but nothing happens, why? In your routing file you have to specifiy the controller with his service name (my_bundle_controller_index) and not his bundle namepath. Change your routing file to:
# src/MyBundle/Resources/config/routing.yml 
my_bundle_route:  
  pattern: my/bundle/route  
  defaults: 
    _controller: my_bundle_controller_index:indexAction // not: MyBundle:IndexController

Now everything should work, besides there are some more things you should know:
  • Your controller should implement the interface ContainerAware or the base symfony2 controller class symfony\Bundle\FrameworkBundle\Controller\Controller
  • Your dependency injection extension should load all required configuration files (especially the one with the service vonfiguration)
  • Your bundle routes are registered in the app/config/routing.(yml|xml|php):
    # app/config/routing.yml 
    my_bundle:
        resource: "@MyBundle/Resources/config/routing.yml"
  • You loaded your bundle config in your app/config(_%environment%).(yml|xml|php):
  • # app/config.yml 
    my_bundle.config: ~