Symfony2 Cache Clear in Prod Fails? Using JMSDiExtraBundle?

Just a quick one out there as I saw a bunch of posts trying to get around the following error:

 $ php app/console cache:clear --env=prod
Clearing the cache for the prod environment with debug true
PHP Fatal error:  Cannot redeclare class EnhancedProxy_19abbe87dfe07b1a52563cac33dd823f08c79506\__CG__\Setfive\XXBundle\Controller\AwesomeController in /home/sf/sf/app/cache/pro_/jms_diextra/proxies/Setfive-XXBundle-Controller-AwesomeController.php on line 46

The error of the occurs on the cache warmup part of the clearing. After looking around people referenced a bunch of different solutions, the most popular being to do a –no-warmup. However, we wanted to try to fix the problem rather than just avoid warming the cache.

The solution fairly quick and really easy. This is using 2.1.X of Symfony and 1.3.* of the JMSDiExtraBundle. Before we had in our AppKernel.php the following:

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Symfony\Bundle\MonologBundle\MonologBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
            new JMS\DiExtraBundle\JMSDiExtraBundle($this),
            new JMS\AopBundle\JMSAopBundle(),
            ....


        );

        if (in_array($this->getEnvironment(), array('dev', 'test'))) {
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
            $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
            $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
        }

        return $bundles;
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
    }
}

I noticed in one of the docs for the JMSDiExtraBundle it has the JMSAopBundle in the list BEFORE JMSDiExtraBundle. We tried moving this up and had success, so the final one looked like:

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Symfony\Bundle\MonologBundle\MonologBundle(),
            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
            new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
            new JMS\AopBundle\JMSAopBundle(),
            new JMS\DiExtraBundle\JMSDiExtraBundle($this),
...
        );

        if (in_array($this->getEnvironment(), array('dev', 'test'))) {
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
            $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
            $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
        }

        return $bundles;
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
    }
}

Hope this saves someone some time.

Symfony2: Configuring VichUploaderBundle and Gaufrette to use AmazonS3

Last week, I was looking to install the VichUploaderBundle into a Symfony2 project to automatically handle file uploads. As I was looking through the Vich documentation I ran across a chunk describing being able to use Gaufrette to skip the local filesystem and push files directly to Amazon S3. Since we’d eventually need to load balance the app and push uploaded files to S3 anyway, I decided to set it up out of the gate. Unfortunately, the documentation for setting up Vich with Gaufrette is a bit opaque so here’s a step by step guide to getting it going.

Install Everything

The first thing you’ll want to do is install all the required packages. If you’re using Composer, the following will work:

ashish@ashish:~/composer require amazonwebservices/aws-sdk-for-php:dev-master
ashish@ashish:~/composer require KnpLabs/Gaufrette:dev-master
ashish@ashish:~/composer require knplabs/knp-gaufrette-bundle:dev-master
ashish@ashish:~/composer require vich/uploader-bundle:dev-master

Once all the packages are installed, you’ll need to configure *both* Gaufrette and Vich. This is where the documentation broke down a bit for me. You’ll need your Amazon AWS “Access Key ID” and “Secret Key” which are both available at https://portal.aws.amazon.com/gp/aws/securityCredentials if you’re logged into AWS.

Configure It

# app/config/parameters.yml
parameters:
...
    aws_key: "YOUR_AWS_KEY"
    aws_secret_key: "YOUR_AWS_SECRET"


# app/config/services.yml
# These are pulling from parameters.yml but you could hardcode them in
services:
...
    ct_file_store.s3:
        class: AmazonS3
        arguments:
            options: { key: %aws_key%, secret: %aws_secret_key% }

# app/config/config.yml
knp_gaufrette:
    stream_wrapper: ~
    adapters:
        uploads:
            amazon_s3:                  
                amazon_s3_id: ct_file_store.s3 #this needs to match the "service" you defined above
                bucket_name: somebucket-name
                options:
                    create: true            
    filesystems:
        amazon_s3:
            adapter:    uploads

vich_uploader:
    db_driver: orm
    gaufrette: true
    storage: vich_uploader.storage.gaufrette
    mappings:
        logo:
            uri_prefix: https://s3.amazonaws.com/somebucket-name # you'll need this set to use the Vich URL generator
            upload_destination: amazon_s3
            namer: vich_uploader.namer_uniqid
            delete_on_remove: true
            delete_on_update: true

Once everything is configured at the YAML level, the final step is adding the Vich annotations to your entities.

<?php

namespace Setfive\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Gedmo\Mapping\Annotation as Gedmo;

// Make sure you add this
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * Brand
 *
 * @ORM\Table(name="brand")
 * @ORM\Entity
 * @Vich\Uploadable
 *
 * Make sure you add this annotation or Vich fails silently.
 */
class Brand
{

    /**
     * @var string
     *
     * @ORM\Column(name="logo", type="string", length=255, nullable=true)
     */
    private $logo;

    /**
     * @Assert\File(
     *     maxSize="5M",
     *     mimeTypes={"image/png", "image/jpeg", "image/pjpeg"}
     * )
     * @Vich\UploadableField(mapping="logo", fileNameProperty="logo")
     *
     * @var File $logo_virtual
     *
     * This is the virtual field that will populate logo with the resulting file.
     */
    protected $logo_virtual;


...

}

Make sure you add the “@Vich\Uploadable” annotation to your Entity or Vich will fail silently.

The “mapping” specified in “@Vich\UploadableField(mapping=“logo”, fileNameProperty=“logo”)“ needs to match the value under “vich_uploader.mappings” which you defined in config.yml

Finally, one last “gotcha” to be cognizant of is this bug - https://github.com/dustin10/VichUploaderBundle/issues/123. Since Vich uses Doctrine lifecycle callbacks to manage files, if no Doctrine fields are changed then the Vich code isn’t executed. The easiest way to get around this (and what we used), is just to manually update the “updated_at” column every time a form is submitted to ensure that the upload handling code is executed.

Anyway, as always, questions and comments are welcome.

Symfony2 and Ordering of Relations and Collection Form Fields

Recently I was working on a project where I kept finding myself ordering a relation over and over by other than something than ID order (ie id= 1,2,3,4,5). For example, I always wanted my relation to be ordered by the ‘name’ field, rather than the ID or order it was inserted into the DB. Let’s take this schema as an example:

CREATE  TABLE IF NOT EXISTS `post` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(32) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB


CREATE  TABLE IF NOT EXISTS `post_attachment` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(32) NULL ,
  `url` VARCHAR(32) NULL ,
  `post_id` INT NOT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `fk_post_attachment_post_idx` (`post_id` ASC) ,
  CONSTRAINT `fk_post_attachment_post`
    FOREIGN KEY (`post_id` )
    REFERENCES `post` (`id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = InnoDB

The issue is each time I attempted:

<?php
$attachments = $post->getPostAttachments();
foreach($attachments as $attachment)
{
     echo $attachment->getId().' '.$attachment->getName()."\n";
}

//Output
// 1 d name
// 2 c name
// 3 a name
// 4 b name

I wanted the output to be in alphabetical order for example. To make this the default for that relation you can add the following annotation to your ‘Post’ entity:

<?php
//MyBundle/Entity/Post.php
...
class Post
{
    ...
    /**
     * @ORM\OneToMany(targetEntity=PostAttachment",mappedBy="post")
     * @ORM\OrderBy({"name"="ASC"})
     */
     private $post_attachments;
    ...


}

Now if you do “$post->getPostAttachments()” they’ll be automatically in order. The ‘@ORM\OrderBy’ column takes care of the ordering automatically. You can specify as many columns on the relation as you’d like there. In addition, this will make it so that all form collections on post with post_attachments are also ordered by name, rather than ID. This affects the relation call every time. If you are only looking into having it some of the time, look into using the repository to do the ordering for those calls.

Symfony2: A Few Slideshares Worth Checking Out

Earlier this week, a buddy of mine reached out looking for interesting Symfony2 resources that went beyond the “basic” tutorial type content. He was looking to really get into the “nitty gritty” of the framework, how larger projects are using it, and hopefully understand some of the philosophy behind service oriented architectures, dependency injection, and behavior driven development.

Not wanting to leave him hanging, Daum and I took to Slideshare to compile a list of presentations that we thought demonstrated some of these concepts well. Anyway, here is the list we came up with.

How Kris Writes Symfony Apps You’ve seen Kris’ open source libraries, but how does he tackle coding out an application? Walk through green fields with a Symfony expert as he takes his latest “next big thing” idea from the first line of code to a functional prototype. Learn design patterns and principles to guide your way in organizing your own code and take home some practical examples to kickstart your next project.

Practical BDD with Behat and Mink An introduction into behavior-driven development with Behat and Mink. A Symfony2 application is used for examples. This was presented in the Top Shelf PHP tutorial at OSCON 2011: http://www.oscon.com/oscon2011/public/schedule/detail/18980 There were some issues converting from ODP, so a PDF version is here: http://jmikola.net/slides/20110725_bdd.pdf

BDD in Symfony2 Quality assurance is one of the most difficult things to implement around software development. Most of time it is left for the final phase of development and very often overlooked entirely. As many experienced web development teams already know, QA needs to be part of the development process from the get-go. Behavior development/testing is just one aspect of quality assurance. And we’ll talk about that.

Being Dangerous with Twig Twig - the PHP templating engine - is easy to use, friendly and extensible. This presentation will introduce you to Twig and show you how to extend it to your bidding.

OpenSky Infrastructure

Dependency Injection in PHP 5.3/5.4

If you have other presentations you think we should check out, leave them in the comments or shoot us a tweet @setfive.

Symfony2: YAML to expose a service inside SonataAdminBundle

The documentation for the SonataAdminBundle mentions how to expose services inside your Admin class using XML:

<service id="sonata.news.admin.post" class="%sonata.news.admin.post.class%">
    <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="post"/>
    <argument />
    <argument>%sonata.news.admin.post.entity%</argument>
    <argument>%sonata.news.admin.post.controller%</argument>

    <call method="setUserManager">
        <argument type="service" id="fos_user.user_manager" />
    </call>

</service>

But unfortunately it doesn’t detail how to do it YAML. Turns out its pretty straightforward, the “gotcha” of course being you need to add the “@” to indicate a service:

ct.be.admin.users:
  class: CT\BEBundle\Admin\UserAdmin
  tags:
	- { name: sonata.admin, manager_type: orm, group: Manage User Settings, label: Users }
  arguments: [null, CT\BEBundle\Entity\User, "SonataAdminBundle:CRUD"]
  calls:
	- [setUserManager, [@fos_user.user_manager]]