PizzaBundle Hacking Day

Here we are going to disect what is going on with beberlei’s PizzaBundle. The reason is that I have ran into the following problem.

The problem at hand is that we don’t know how to render a form where there is a relationship of OneToMany to a collection but where we want to display fields for only one new entity each time we persist.

So we start by installing PizzaBundle in an effort to dissect the app:

So the form I found to be rather *simple* on pizzabundle. It is true that it has some nice javascript and all but i cannot for instance customize the inners of the pizza. My problem involves customizing exactly one pizza, but the inner parts say price and type/name then that gets added or persisted so my problem is somewhat similar but a little bit extra is needed so I guess i am going to have to hack it like they do.

So let’s look on the index action for the controller that sets the main start form for entering the orders on PizzaBundle. Everything seems normal until you see that what is being passed as second argument to the createForm method is not an entity as we normally but a form factory which is created for the order form type passed as well. In addition what is persisted when the form is valid is the entity which is product of the $factory->make():

 public function indexAction()
    {
        $em = $this->getDoctrine()->getEntityManager();
 
        $factory = new OrderFactory($em);
 
        $form = $this->createForm(new OrderFormType(), $factory);
 
        $request = $this->getRequest();
 
        if ('POST' === $request->getMethod()) {
 
            $form->bindRequest($request);
 
            if ($form->isValid()) {
 
                $em->persist($order = $factory->make());
                $em->flush();
 
                $this->get('session')->setFlash('success', 'New order were saved!');
 
                return $this->redirect($this->generateUrl('acme_pizza_order_show', array(
                    'id' => $order->getId(),
                )));
            }
        }
 
        return array('form' => $form->createView());
    }

Now let’s look more in depth what is inside the OrderFormType. Basically we see that the builder adds elements not necessarily associated with an entity in particular but rather this is the form model part (first part) that assigns the fields that the form will render:

 public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('known_customer', 'checkbox', array(
                'required' => false,
            ))
            ->add('known_phone', 'text')
            ->add('customer', new Type\CustomerType())
            ->add('items', 'collection', array(
                'type'         => new Type\OrderItemType(),
                'allow_add'    => true,
                'allow_delete' => true,
                'prototype'    => true,
            ))
        ;
    }

The second part of the form model corresponds to what is being implemented by the $factory. This $factory part is the one in charge of not assigning the fields of the form to be rendered but rather talk of how the form fields relate to what is being persisted in terms of entities as the $em->persist() call only understands ORM object persistence lingo.

The $factory definition is located under Entity folder and under a Factory folder. Not sure if this is a standard practice, however what I do think is that the name should rather be called FormModel, as it deals with the Form component but it is not precisely an entity.

The factory name is justified in a sense because it creates or builds the entity inside the method make of the factory passed.

/**
 * @return \Acme\PizzaBundle\Entity\Order
 */
public function make()
{
    $order = new Order();
    $order->setCustomer($this->customer);
 
    foreach ($this->items as $item) {
        $order->addItem($item);
    }
 
    return $order;
}

As we can see here the object Order is prepared built and shipped out with the elements that are received by the form, that is: customer and $items.

In addition to this there is some kind of custom validation that is performed by the form model class or factory class. The validation is indicated mainly over annotations:

/**
 * @Assert\callback(methods={"isValidCustomer", "pickedOrderItems"})
 */

The methods then are implemented respectively and use the ExecutionContext class $context to return violations or simply just return true or false.

At this point it is good remembering also that the definition for createForm is really:

return $this->container->get('form.factory')->create($type, $data, $options);

And that $type and $data are very independent on any persistence we can handle outside of this service.

Finally I would like to end this article posing the limitations the the current approach has. It is not very DRY because it tries to reinvent the wheel parts already implemented in the forms component. For overcoming this limitation please see the other blog post on Extending Forms.

If you have found this article helpful please donate with the link above. Thanks!

Behat – Mink New Use Of Aliased Subcontexts & Complex Step Chaining

Organization for reuse and refactoring is needed when the number of step definitions inside our context file gets big. Behat allow us to (a) organize steps definitions for our contexts into a main context and several subcontexts, and to (b) abstract larger tasks with repetitive steps via step chaining. These two characteristics combined can make our features very versatile and reusable.

Aliased Subcontexts

To try the feature just load the aliased-subcontext branch by inserting the following line on deps:

version=origin/feature/aliased-subcontexts

The line goes right inside the behat deps specification. In the future this branch feature will be merged into production so this step will not be needed.

Because of our update code example breaks with:

Catchable Fatal Error: Argument 2 passed to Behat\Behat\Context\BehatContext::useContext() must implement interface Behat\Behat\Context\ExtendedContextInterface, none given, called in /home/cordoval/sites-2/Bundle/Features/Context/FeatureContext.php on line 28 and defined in /home/cordoval/sites-2/vendor/behat/Behat/src/Behat/Behat/Context/BehatContext.php line 39

In order to bring the functionality back the new use of context and subcontext should be rendered in the following way. The example will illustrate a main context which extends MinkContext and also loads two BehatContext subcontexts aliased ‘clicking’ and ‘loading’.

class FeatureContext extends MinkContext
{
    public function __construct(HttpKernelInterface $kernel) {
        $this->useContext('clicking', new ClickingContext($kernel));
        $this->useContext('loading', new LoadingContext($kernel));
        parent::__construct($kernel);
    }

The fetching of session within the subcontext class ‘clicking’ requires a call to getMainContext as follows:

class ClickingContext extends BehatContext{
    /**
     * @Then /^I click complete button$/
     */
    public function iClickCompleteButton()
    {
        $page = $this->getMainContext()->getSession()->getPage();
        // ...
    }

And in addition, from the ‘loading’ subcontext one can call methods from ‘clicking’ subcontext via:

class LoadingContext extends BehatContext{
   /**
    * @Given /^I\'m already logged in$/
    */
   public function imAlreadyLoggedIn()
   {
        // this calls a method from the main context
        $this->getMainContext()->iWaitForTheSuggestionBoxToAppear();
 
        // this calls a method from a different subcontext
        $this->getMainContext()->getSubContext('clicking')->iClickCompleteButton();
 
        // ...
   }

Notice that even though you are on the main context you cannot call the methods of a subcontext directly but rather you have to first get that context in order to call its method.

Complex Step Chaining
In order to abstract a step that could comprise more than one individual step or to provide a macro step that is repetitive yet still complex behat allows us to demand from our step the execution of mini-scenarios. An example is found in the behat code itself under features folder at the root of behat package:

/**
 * @Given /I entered "([^"]*)" and expect "([^"]*)"/
 */
public function complexStep($number, $result)
{
    return array(
        new Step\Given("I have entered \"$number\""),
        new Step\When("I press +"),
        new Step\Then("I should see \"$result\" on the screen")
    );
}

We can also chain macro or micro steps using a simple return like:

/**
 * @Given /Я ввел "([^"]*)"/
 */
public function iHaveEnteredRu($number)
{
    return new Step\Given("I have entered \"$number\"");
}

The use of chaining tends to be more application specific whereas the use and grouping of steps in subcontexts tends to enhance readability at the time they are used and promotes the creation of specialized libraries that one can use to thread application scenarios. Planning of these things comes with practice and experience, and things like the auto-generated code done in behat codebase using features are a good example of this. Credits should be given to everzet and stof for taking the time to help me understand more of the basics.

Thanks hope this article finds its place into your learning and feel free to donate if this has helped you.

Symfony2 with Jenkins

Here is a list for common problems found while integrating sf2 and jenkins.

First problem I ran into was that I had set jenkins user on debian to handle all jenkins tasks. git was set to work with my current debian ubuntu account, however when jenkins was working it did not have settings for identity set on git. That was solved running config –global user.email and user.name respetively.

Second problem I ran into was the following warning:

FATAL: Unable to find build script at /var/lib/jenkins/jobs/FormModelProjectBundle/workspace/build.xml

Yes I had to modify build.xml from the website instructions and adapt it and place it at the root of my project folder.

Third problem I found is that I did not have installed ant:

sudo apt-get install ant

After this I got this sucker:

[exec] Cannot find specified rule-set "/var/lib/jenkins/jobs/FormModelProjectBundle/workspace/build/phpmd.xml".

So I added the following under build/ :

<?xml version="1.0" encoding="utf-8" ?>
<phpmd failonerror="off" failonruleviolation="off">
  <ruleset>unusedcode</ruleset>
  <ruleset>codesize</ruleset>
  <ruleset>design</ruleset>
  <ruleset>naming</ruleset>
</phpmd>

Then it was giving me an error saying that it couldn’t find phpunit. And it is because I had to place -c app as argument and path respectively (check the repo for FormModelProjectBundle).

Then I ran into a problem with the classloaders:

[exec] PHP Fatal error:  Class 'Doctrine\Common\Annotations\AnnotationRegistry' not found in /var/lib/jenkins/jobs/FormModelProjectBundle/workspace/app/autoload.php on line 43

This was because i needed to run a bin/vendors update task into the build:

<target name="vendors"
         description="Update vendors">
  <exec executable="bin/vendors" failonerror="true">
     <arg value="update" />
  </exec>
 </target>
 
// and at the end
<target name="build" depends="clean,vendors,parallelTasks,phpunit,phpcb"/>

Then also realized that I had to log somewhere (this code goes inside the phpunit.xml):

<logging>
        <log type="coverage-html" target="build/coverage" title="Name of Project"
            charset="UTF-8" yui="true" highlight="true"
            lowUpperBound="35" highLowerBound="70"/>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
        <log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
</logging>

You can solve many more problems. If you have found more please post them on comments so we can keep track of common issues.

Also I transferred the ant version to a phing build version here so it can work with phpstorm https://github.com/cordoval/FormModelProjectBundle/blob/master/buildphing.xml. This you can use with jenkins.

Pushed to git repo and now running jenkins!

Please consider donating for most sf2 posting!

Some of the giants whose shoulders lifted me to see further:
http://jenkins-php.org/
http://edorian.posterous.com/setting-up-jenkins-for-php-projects
http://blog.symfony.jp/2011/03/19/307/