Henrik Bjørnskov

Mar 26, 2012

In a world where cloud is king and it is dead easy to setup a project the support for hooks and vendor installing is extremly little. Some time ago Pagodabox rolled out hooks functionality which makes it easy to use Composer instead of submodules for your next project - Yes Symfony2 can run on Pagodabox.

:::yaml
web1:
    after_build:
    - "if [ ! -f composer.phar ]; then curl -s http://getcomposer.org/installer | php; fi; php composer.phar install"

The after_build hook is used before the application is packaged, so this means there is write access to the sourcecode. The little bash script check for a composer.phar file and if that is not found, it downloads Composer. And last it install thes dependencies.

Feb 1, 2012

Two of the more complicated components in Symfony2 is the Form and Validator component. The Validator is created in such a way it "always" need an Domain Object with Constraints associated through metadata. This is explained in detail here http://symfony.com/doc/2.0/book/validation.html

But there is another way. A way that resemble's the symfony1 forms. Where you could specify the validations directly in your form class.

The FormTypeInterface::getDefaultOptions(array $options) have an option called validation_constraint which together with Constraints\Collection can be used to validate the form data.

An example is always better than a 1000 words (well most of the time) so here is a full blown example of a SessionType.

::php
<?php

namespace Foobar\BarBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Choice;

/**
 * @author Henrik Bjornskov <[email protected]>
 */
class SessionType extends AbstractType
{
    /**
     * @param FormBuilder $builder
     * @param array $options
     */
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('_username', 'text')
            ->add('_password', 'password')
            ->add('_remember_me', 'checkbox')
        ;
    }

    /**
     * @return array
     */
    public function getDefaultOptions(array $options)
    {
        return array(
            'intention' => 'authenticate',
            'validation_constraint' => new Collection(array(
                'fields' => array(
                    '_username' => array(
                        new NotBlank(),
                        new Email(),
                    ),
                    '_password' => new NotBlank(),
                    '_remember_me' => new Choice(array(
                        'choices' => array(
                            true,
                            false,
                        ),
                    )),
                ),
            )),
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'session';
    }
}
Dec 29, 2011

When talking with @jmikola on #Symfony-dev this afternoon we got into the subject of cross site request forgery and symfony2 login forms. And it seems that form-login already supports this but neither of us knew how it worked. So here is another quick tip. This time about securing you login form from cross site attacks.

To add it to your login form it is needed to add csrf_provider key about what provider you want to use. For this we can use the default one that the Form component uses. Also we can add a csrf_parameter key but theres a default with the value of _csrf_token.

Here is a full example of the config file:

::yaml
# app/config/security.yml
security:
    firewalls:
        default:
            pattern: ^/
            form_login:
                login_path: session_new
                check_path: session_new
                csrf_provider: form.csrf_provider

Obviously now when you submit a login form it wont work because they token is not getting printed out. So lets fix that with a couple of lines of code.

First the controller action. The string sent to the generateCsrfToken method is used for generation. It is the "intention" and authentication is the default for form login.

::php
<?php

class SecurityController
{
    public function loginAction()
    {
        // Here goes all the normal stuff like getting the last username and error
        // so we are playing $username and $error is already set.

        $csrfToken = $this->container->get('form.csrf_provider')->generateCsrfToken('authentication');

        return $this->container->renderResponse('MyBundle:Security:login.html.twig', array(
            'username' => $username,
            'error' => $error,
            'csrf_token' => $csrfToken,
        ));
    }
}

Last but not least the template

::jinja
{% extends '::base.html.twig' %}

{% block body %}
    <form action="{{ path('my_security_login') }}" method="post">
        <input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />

        {# Rest of the input fields and a submit button #}
    </form>
{% endblock %}
Dec 24, 2011

Earlier when playing around with the Security component and SecurityBundle i found that for all paths you can specify a route name and the component will match it when check for the request paths. The following example shows how easy it is.

::yaml
# app/config/security.yml
security:
    firewalls:
        default:
            pattern: ^/
            form_login:
                login_path: session_new
                check_path: session_new
            logout:
                path: session_delete
                target: homepage

So now you do not have to change your security configuration everytime you change the url of your application.

It does not work, you liar!?

Infact it does, but check_path is validated with the pattern specified for your firewall. This happens so early in the request cycle that access to all routes are not available yet. So until that is fixed you should uncomment line 277 in SecurityBundle/DependencyInjection/MainConfiguration.php if you want it to work.

Dec 23, 2011

When using Symfony2's Security component you can at first feel it is a bit bloated and too complex for the job. But under the hood there is a powerful and every extendable product which proved useful today.

I needed to only allow anonymously authenticated users access to pages such as /login and /register. To do this there is two methods (that i know of). The first one is to use JMSSecurityExtraBundle which have a feature called expressions that generates php code from human reable text (almost human readably)

::yaml
# app/config/security.yml
security:
    access_control:
        - { path: /login, access: "isAnonymous()" }

The other way is to implement a custom voter. This is a bit more work, but for my solution it is perfect (i dont want to rely on much magic). The voter takes inspiration from AuthenticatedVoter which is a part of core.

::php
<?php

namespace FooBar\FooBundle\Security\Authorization\Voter;

use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

/**
 * @author Henrik Bjornskov <[email protected]>
 */
class AnonymousVoter implements VoterInterface
{
    /**
     * The role check agains
     */
    const IS_ANONYMOUS = 'IS_ANONYMOUS';

    /**
     * @var AuthenticationTrustResolverInterface $authenticationTrustResolver
     */
    protected $authenticationTrustResolver;

    /**
     * @param AuthenticationTrustResolverInterface $authenticationTrustResolver
     */
    public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver)
    {
        $this->authenticationTrustResolver = $authenticationTrustResolver;
    }

    /**
     * @param string $attribute
     * @return Boolean
     */
    public function supportsAttribute($attribute)
    {
        return static::IS_ANONYMOUS === $attribute;
    }

    /**
     * @param string $class
     * @return Boolean
     */
    public function supportsClass($class)
    {
        return true;
    }

    /**
     * Only allow access if the TokenInterface isAnonymous. But abstain from voting
     * if the attribute IS_ANONYMOUS isnt supported.
     *
     * @param TokenInterface $token
     * @param object $object
     * @param array $attributes
     * @return integer
     */
    public function vote(TokenInterface $token, $object, array $attributes)
    {
        foreach ($attributes as $attribute) {
            if (!$this->supportsAttribute($attribute)) {
                continue;
            }

            // If the user is anonymous then grant access otherwise deny!
            if ($this->authenticationTrustResolver->isAnonymous($token)) {
                return VoterInterface::ACCESS_GRANTED;
            }

            return VoterInterface::ACCESS_DENIED;
        }

        return VoterInterface::ACCESS_ABSTAIN;
    }
}

To use this snippet, save it somewhere in your project and modify the namespace to match. And then use the following as a template to register it with the DependencyInjection Container.

::xml
<container xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd" xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <parameters>
        <parameter key="foobar.security.authorization.voter.anonymous.class">FooBar\FooBundle\Security\Authorization\Voter\AnonymousVoter</parameter>
    </parameters>

    <services>
        <service id="security.access.anonymous.voter" class="%foobar.security.authorization.voter.anonymous.class%" public="false">
            <tag name="security.voter"/>
            <argument type="service" id="security.authentication.trust_resolver"/>
        </service>
    </services>
</container>

also remember to add it to the config.

::yaml
# app/config/security.yml
security:
    access_control:
        - { path: /login, roles: IS_ANONYMOUS }
        - { path: /forgot-password, roles: IS_ANONYMOUS }
        - { path: /register, roles: IS_ANONYMOUS }

And just because test are a good thing and awesome. Here is the corresponding test.

::php
<?php

namespace FooBar\FooBundle\Tests\Security\Authorization\Voter;

use FooBar\FooBundle\Security\Authorization\Voter\AnonymousVoter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

/**
 * @author Henrik Bjornskov <[email protected]>
 */
class AnonymousVoterTest extends \PHPUnit_Framework_TestCase
{
    public function testSupportsClass()
    {
        $voter = new AnonymousVoter($this->createResolverMock());
        $this->assertTrue($voter->supportsClass('stdClass'));
        $this->assertTrue($voter->supportsClass('Just return true always'));
    } 

    public function testSupportsAttribute()
    {
        $voter = new AnonymousVoter($this->createResolverMock());
        $this->assertTrue($voter->supportsAttribute(AnonymousVoter::IS_ANONYMOUS));
        $this->assertFalse($voter->supportsAttribute('ROLE_USER'));
        $this->assertFalse($voter->supportsAttribute('IS_AUTHENTICATED_ANONYMOUSLY'));
    }

    public function testVote()
    {
        $token = $this->createTokenMock();

        // Voter returns VoterInterface::ACCESS_ABSTAIN when its attribute isnt found
        $voter = new AnonymousVoter($this->createResolverMock());
        $this->assertEquals(VoterInterface::ACCESS_ABSTAIN, $voter->vote($token, new \stdClass, array()));

        // Voter returns ACCESS_DENIED if token is not anonymous and our attribute is present
        $this->assertEquals(VoterInterface::ACCESS_DENIED, $voter->vote($token, new \stdClass, array(
            AnonymousVoter::IS_ANONYMOUS,
        )));

        // Voter returns ACCESS_GRANTED if the token is anonymous and our attribute is present
        $voter = new AnonymousVoter($this->createResolverMock(true));
        $this->assertEquals(VoterInterface::ACCESS_GRANTED, $voter->vote($token, new \stdClass, array(
            AnonymousVoter::IS_ANONYMOUS,
        )));
    }

    protected function createResolverMock($isAnonymous = false)
    {
        $resolver = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface');
        $resolver
            ->expects($this->any())
            ->method('isAnonymous')
            ->will($this->returnValue($isAnonymous))
        ;

        return $resolver;
    }

    protected function createTokenMock()
    {
        return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
    }
}
Dec 14, 2011

I must admit, my earlier post about Stampie (my email api wrapper) wasn't that explanatory. This blog post will hopefully rectify this.

Introduction

So what is Stampie. Stampie is a API wrapper for the most common email sending services. It provides a standard PHP Api to send emails. But mostly it is a project to test TDD and experiment with a couple of different things.

Stampie is developed with Dependency Injection and therefore there is a lot of objects. At the start it can be quite cumbersome, but will make a lot of sense if you start to develop and add additional provid

Demonstration

To get a good feel of how it works and how the internals are laid out a demonstration says a 1000 things more than 1000 words.

In this example we will use a HTTP Library for PHP developed by Kriss Wallsmith called Buzz. Buzz is a lightweight library that sends request and unifies the response in an object.

The service provider we will use is SendGrid. SendGrid is one of many providers supported.

::php
<?php

// stampie.phar sets up autoloading with spl_autoload_register
require '/path/to/stampie.phar';

class Message extends \Stampie\Message
{
    public function getFrom() { return '[email protected]'; }
    public function getSubject() { return 'You are trying out Stampie'; }
    public function getText() { return 'So what do you think about it?'; }
}

$adapter = new Stampie\Adapter\Buzz(new Buzz\Browser());
$mailer = new Stampie\Mailer\SendGrid($adapter, 'username:password');

// Returns Boolean true on success or throws an HttpException for error
// messages not recognized by SendGrid api or ApiException for known errors.
$mailer->send(new Message('[email protected]'));

Internals

As mentioned earlier Stampie uses Dependency Injection. But Stampie also heavily uses the Adapter Pattern for its Mailers and Adapters.

Providers

Currently Stampie is bundled with 3 providers. Theese are:

But Stampie is not limited to theese providers. Stampie is extendable enough to also support Amazon SES, MailGun, CritSend and so on. All that is needed is a MailerInterface implementation for their API if they provide one over HTTP. Later an some points and an example to create a custom provider is explained.

Adapters

Because all the popular PHP frameworks have their own popular HTTP client Stampie have an AdapterInterface that offers integration to thoose HTTP Clients. Currently there is only 2 clients supported.

  • Buzz - The library used in the demonstration and the most populair one in the Symfony community.

  • Guzzle - A more feature complete HTTP library.

As with Mailers the same is true for adapters. If you need an Adapter for a HTTP Client that isn't already support all that is needed is an implementation of AdapterInterface.

Tests

Stampie is heavily tested with PHPUnit. PHPUnit is the defacto standard for testing in the PHP world and for Symfony. To ease the testing Stampie have a lot of Interfaces.

Tests are also the reason for Dependency Injection usage.

Stampie is tested continuously on every push to GitHub with Travis.

Resources are not yet implemented.

The goal for code coverage is \~90%. This also means that contributions and bug reports should include tests.

Frameworks

As Stampie dosn't rely on any thing else that it self and a HTTP Client. It has no ties to a Framework. This makes it easy to incorporate it to your framework of choice. Below is a list of current framework integrations.

Symfony

Symfony integration is provided by HBStampieBundle. The bundle provides the Mailer as a service. Currently it only supports the usage of Buzz.

Feedback

If you have any feedback for Stampie or about this document or anything else you want to see. Shoot me an tweet on @henrikbjorn or in a Bug Report on GitHub.

Do you have a new Adapter or Mailer and want it to be included? Send a Pull Request on GitHub, hopefully with tests included (that pass).

Nov 29, 2011

Please welcome Stampie on the block. It is a small library i created for using Postmark for my Symfony application.

There is nothing much to say about it other than it Supports Postmark and SendGrid together with Guzzle and Buzz as the backend client.

The readme page says it all about how to use it. And if you find any bugs, please report them and/or do a Pull Request (They are awesome!) otherwise you are welcome to leave a comment.

Why did i create my own?

Becuse all the others out there just SUCKS (Steve Jobs said it and so can i!)

Nov 15, 2011

The other day the Continuous integration testing service travis-ci.org announced they had integrated PHP into their service. Which is wonderful news and with some of the Symfony2 bundles from FriendsOfSymfony already added together with Doctrine2 and others quickly following them.

To integrate your project with travis the only thing necesarry is to have a .travis.yml file and a working PHPUnit test setup like http://github.com/simplethings/SimpleThingsFormExtraBundle. Where the Tests/vendors.php script is executed before the tests are perfomed. But it would be way cooler to just have Composer handle the autoloading and dependencies. So here is a .travis.yml file that uses Composer to get the dependencies and generate the autoloading.

::yaml
language: php

php:
  - 5.3
  - 5.4

before_script:
    - wget http://getcomposer.org/composer.phar
    - php composer.phar install

Very simple and straight forward. The following is happening before each test run.

  1. Download composer.phar from http://getcomposer.org
  2. Get the dependencies with composer by invoking its install command.

Next it is needed to add the following to your bootstrap.php file so Composer's autoloading is used.

::php
<?php

if (!@include __DIR__ . '/../vendor/.composer/autoload.php') {
    die(<<<'EOT'
You must set up the project dependencies, run the following commands:
wget http://getcomposer.org/composer.phar
php composer.phar install
EOT
    );
}

And last but not least add the vendor and other related files to your .gitignore file.

vendor
composer.phar
composer.lock

To test it out your self go get Composer, install your dependencies and run it with phpunit like so

::bash
$ phpunit
Nov 8, 2011

Personally i have never been to found of managing PHP Packages through pear of by having some version control system inclusion of my dependencies (svn:externals, git submodules and so on). Mostly because of PEAR having dependencies and package.xml defined as xml. I HATE xml with a passion.

But now there is a new library/tool starting to form and fix thoose things. It is called Composer and uses a composer.json file to handle packages. This rocks for 2 reasons.

  1. JSON is incredible easy to read and indent.
  2. Everybody knows this syntax from Javascript and/or Twig (Arrays and Hash literals)

Because this tool is new there isnt a lot of packages. But most of the Symfony2 community and the other people who have started playing with the tools have put packages up on Packagist.org. Which currently is the main place to find packages.

I encourage all to go play with it. And join #composer on Freenode.

The dokumentation is currently rather sparse so here is an example composer.json file which is from one of my Symfony2 projects.

::json
{
    "name" : "henrikbjorn/super-secret",

    "autoload" : {
        "psr-0" : {
            "SuperSecret" : "src/",
            "Twig_"       : "vendor/twig/twig/lib"
        }
    },

    "require": {
        "php"                  : ">=5.3.0",

        "twig/twig"            : ">=1.3",
        "symfony/symfony"      : ">=2.1",
        "doctrine/mongodb-odm" : "*",

        "symfony/mongodb-odm-bundle" : "master-dev"
    }
}

Some of the more fun and exiting features are.

  • Automatic autoload.php generation by psr-0 standard. Just include vendor/.composer/autoload.php.
  • Package resolvement. Add doctrine/orm and get doctrine/dbal and doctrine/common for free.
  • Possible to overwrite the target path. This allows for Symfony2 bundles (and other) to have the correct path for their loading needs.
  • Uses PEAR, Git, GitHub as repository options. This means you can also install that PEAR package you depend on.
  • hopefully a lot more to come.
Oct 17, 2011

Update: The real code for our Wizard with Working example and tests have been released at github.com/Peytz/Wizard

It happens that in #symfony-dev there is a question about multistep forms and wizards and how to do them. Obviously there are different answers dependending on the implementation requirements.

The easy ways is doing it with Javascript and just show/hide the correct fieldsets when needed. The downside with this approach is that the data is only saved and validated once at the end. So if the user reloads the page the entered information is gone :(.

The other way is to have every Step in the Wizard being a seperate form and validate the data based on what step you are on and save the necesarry fields.

How i did it for a work project

When tasked with doing a application for calculating reports for a customer at work i sat down and thought about how it would work with the requirement being non javascript. The solution is quite simple.

With inspiration from Fabien Potencier's SensioDistributionBundle i decided to create a Manager (Wizard class) which have Steps.

A Step is an interface with methods to return the correct names, forms and templates.

A Wizard gets injected a ReportInterface object in its controller. a ReportInterface object is the Entity that will be persisted when a step have been completed.

StepInterface

::php
<?php

namespace Acme\DemoBundle\Wizard;

interface StepInterface
{
    function getFormType();

    function isVisible(ReportInterface $report);

    function getName();
}

ReportInterface

::php
<?php

namespace Acme\DemoBundle\Wizard;

interface ReportInterface
{
}

Wizard

::php
<?php

namespace Acme\DemoBundle\Wizard;

class Wizard implements \IteratorAggregate
{
    protected $report;

    protected $steps = array();

    public function __construct(ReportInterface $report)
    {
        $this->report = $report;
    }

    public function add(StepInterface $step)
    {
        $this->steps[$step->getName()] = $step;
    }

    public function getReport()
    {
        return $this->report;
    }

    public function getTemplatePathByStep(StepInterface $step)
    {
        return sprintf('AcmeDemoBundle:Wizard/steps:%s.twig.html', $step->getName());
    }

    public function get($step)
    {
        // Obviously do a check on the index here.
        return $this->steps[$step];
    }

    public function all()
    {
        return $this->steps;
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->all());
    }
}

Using it in a Symfony context.

As it should be quite clear for the average developer, it is needed to implement a Wizard, Steps and a Report for every Wizard you need.

::php
<?php

namespace Acme\DemoBundle\Controller;

class DemoController extends Controller
{
    protected function getWizard()
    {
        // Should get the ReportInterface $report from Doctrine
        return new CustomWizard(new Report());
    }

    public function wizardAction($step)
    {
        $wizard = $this->getWizard();
        $report = $wizard->getReport();
        $step = $wizard->getStep($step);

        if (!$step || !$step->isVisible($report)) {
            throw $this->createNotFoundException('Step Not Found or Not Visible');
        }

        $form = $this->createForm($step->getFormType(), $report, array(
            'validation_groups' => array($step->getName()),
        ));

        // Bind the form if the request is valid
        if ($form->isValid()) {
            // Persist with Doctrine
        }

        return $this->render($wizard->getTemplatePath($step), compact('form', 'wizard', 'report', 'step'));

    }
}

The controller example is simple and incomplete but should give you a general overview of how it could be done, and how i have done it with my project.