Symfony 2 and Bootstrap 3 Assetic Config and Base HTML Template

This doesn’t mean much standalone, but it’s here for future reference.

# Assetic Configuration
assetic:
    debug:          %kernel.debug%
    use_controller: false
    bundles:        [ ]
    java: /usr/bin/java
    filters:
        cssrewrite: ~
        cssembed:
          jar: %kernel.root_dir%/Resources/java/cssembed-0.4.5.jar
        yui_js:
          jar: %kernel.root_dir%/Resources/java/yuicompressor.jar
        lessphp:
          file: %kernel.root_dir%/../vendor/leafo/lessphp/lessc.inc.php
          apply_to: "\.less$"
    assets:
        jquery_js:
            inputs:
                - '%kernel.root_dir%/../components/jquery/jquery.min.js'
            filters: [?yui_js]
        bootstrap_js:
            inputs:
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/transition.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/alert.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/modal.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/dropdown.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/scrollspy.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/tab.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/tooltip.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/popover.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/button.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/collapse.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/carousel.js'
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/js/affix.js'
            filters: [?yui_js]
        bootstrap_less:
            inputs:
                - '%kernel.root_dir%/../vendor/twitter/bootstrap/less/bootstrap.less'
            filters: [lessphp,cssembed]

Original source from Joeri Verdeyen, slightly modified for Bootstrap 3.


< html>
< head>
    < title>{% block title %}Bootstrap 101 Template{% endblock %}< /title>
    < meta name="viewport" content="width=device-width, initial-scale=1.0">
    

    {% block stylesheets %}
        {% stylesheets '@bootstrap_less' combine=true %}
            
        {% endstylesheets %}
    {% endblock %}

    
    
< /head>
< body>

    {% block myBody %}
    {% endblock %}

    {% block javascripts %}
        {% javascripts '@jquery_js' '@bootstrap_js' filter='?yui_js' combine=true %}
            
        {% endjavascripts %}
    {% endblock %}
< /body>
< /html>

(Sorry, had to put the spaces in to get the whole thing to show. Silly WordPress. Thanks also Gzom!)

PHPNW2013 is Live

phpnw-2013-logoThe one conference I have been waiting for since I missed out on attending last years due to work, is PHP North West 2013.

I’m really keen to actually speak at this event, but I don’t know what topic to speak about. Something Symfony 2 related would be a good start I should imagine.

When you attend a conference like this, what is the one topic that you would like to learn more about that never seems to be covered?

Please leave your suggestions in the comments box. And if I can come up with something I will post about it here.

How I Use Doctrine Fixtures with Symfony 2 for an Easier Life

John-Bercow

Back in the days of yore, sometime around Yesteryear, I became aware of Doctrine Fixtures.
I’m not going to go into any great depth as to what they are, what they do, or why you should use them, because frankly if you don’t use them you are a silly billy bean.

Anyway, one thing that makes them not so great is that in the default implementation given in the Symfony documentation, you end up with a db table with auto incremented records starting at some crazy number, incrementally higher and higher every time you run the fixtures.

I’m sure there are ways round this, ways I have never imagined. But as I haven’t imagined them, I can’t very well implement them.

So here is my one line wonder (well, one line after a few other lines have been typed once) to always get you back to zero.

Command and ConqueraSillyBean_by_aSillyBean

Sadly this has nothing to do with that bald headed NOD goon Kane, or that commando guy who killed people with one shot. But programming is quite a dull subject when written down, so I have to spice up my posts with references to more fun times.

Anyway, what we actually need is a Symfony Console Command.

Wait, wot? This sounds hard and scary and I can’t really be bothered learning new things right now.

Actually it’s not – it’s really easy, and it saves time. Your time. Time that you can now invest in bettering yourself. Or on Reddit. Whatever.

Ok, I’m just going to paste my code, then walk through it.

namespace MCM\MyExampleBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class PurgeEverythingCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('mcm:purge')
            ->setDescription('Purges the test database, reloads the fixtures, and triggers a test routine.')
            ->addArgument('confirm', InputArgument::REQUIRED, 'You must confirm you wish to erase everything in your test database!')
            ->addOption('notests', null, InputOption::VALUE_NONE, 'Run without the test suite')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // flatten all the tables
        exec('php /full/path/your/site/root/app/console doctrine:schema:drop --env=test --force');

        // recreate the schema
        exec('php /full/path/your/site/root/app/console doctrine:schema:create --env=test');

        // clear any doctrine caches
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-metadata --env=test');
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-query --env=test');
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-result --env=test');

        // re-create the fixtures
        exec('php /full/path/your/site/root/app/console doctrine:fixtures:load -n --env=test');

        if( !$input->getOption('notests') )
        {
            // re-run the tests
            system('phpunit -c app/');
        }
    }

}

Meep, the computer is now telling you exactly what you could do with a life time supply of chocolate.

Ok – you will have gathered from the first line that Commands go in a command sub folder off your Bundle directory. They don’t have too, but it’s a nice convention.

Make sure you suffix your file name with Command.

Then we get to the configuration:

    protected function configure()
    {
        $this
            ->setName('mcm:purge')
            ->setDescription('Purges the test database, reloads the fixtures, and triggers a test routine.')
            ->addArgument('confirm', InputArgument::REQUIRED, 'You must confirm you wish to erase everything in your test database!')
            ->addOption('notests', null, InputOption::VALUE_NONE, 'Run without the test suite')
        ;
    }

The setName method is saying what will we need to type in after app/console to get this command to run. You can put anything here.

The setDescription method is what you will see as a bit of helper text after your string from setName when you type in just app/console and hit return.

The addArgument method sets an argument on the command line that could allow the user to modify the outcome of your script depending on what arguments they choose. In my case, I hide this command initially so a user doesn’t accidentally type in the command and delete everything.

Lastly, the addOption method again allows more granular configuration of how parts of your command will run. In our case, whether or not to run the test suite after recreating the db.

Click here for a full breakdown of all the available ‘actions’ available when using a Command.

And now the actual guts of the how the command executes:

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // flatten all the tables
        exec('php /full/path/your/site/root/app/console doctrine:schema:drop --env=test --force');

        // recreate the schema
        exec('php /full/path/your/site/root/app/console doctrine:schema:create --env=test');

        // clear any doctrine caches
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-metadata --env=test');
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-query --env=test');
        exec('php /full/path/your/site/root/app/console doctrine:cache:clear-result --env=test');

        // re-create the fixtures
        exec('php /full/path/your/site/root/app/console doctrine:fixtures:load -n --env=test');

        if( !$input->getOption('notests') )
        {
            // re-run the tests
            system('phpunit -c app/');
        }
    }

All this is doing is dropping the database in the Test (--env=test) environment; recreating the database schema; clearing any Doctrine caches; and then importing the fixtures into a fresh, empty table structure.

This gets round any pesky foreign key relationship issues when trying to empty tables.

Optionally, the test suite will then be triggered.

The three Doctrine cache clear commands are probably not even needed – but it happens behind the scenes against a test database, so why not?

Downsides

Yeah there are downsides to this sadly.

The biggest downside is that if there are any errors, you will get the error but no context of where the error occurred.

In my case, there are only three possible outcomes.

  1. It all worked so you are just dumped back to your command line
  2. Your fixture import failed – a red box with an error shows up
  3. Your fixture test failed – outputs some combination like …..!…EEEE..

Point one is good. So we move on happier in our lives, more time to post cat pictures to reddit.

Point two is not so good.

In this case you are best to run the schema:drop, schema:create, and fixtures:load commands from the above script again by hand, and see which fixture is bombing out.

Point three is also not so good.

In this case you need to run the test suite manually as it gives a more verbose output of the results.

Lights, Camera, Action-diddly-action-Jackson!

So to finally run this bad boy we just type in:

php app/console mcm:purge --notests confirm

You can omit the --notests option as it’s optional, but the confirm argument is required remember. This is just a fail safe.

So it might seem a little crazy and have a few downsides, but 99% of the time this is a real time saver, so get on it.

Debian – curl: (7) couldn’t connect to host

Bit of an odd one here with a pretty easy fix.

I was trying to install Composer on a fresh Debian server, and kept getting the following error:

myuser@debian-box:/var/www/html/mysite.co.uk$ sudo curl -sS http://getcomposer.org/installer | php
#!/usr/bin/env php
All settings correct for using Composer
Downloading...
Download failed: file_get_contents(https://getcomposer.org/composer.phar): failed to open stream: Connection timed out
Downloading...
Download failed: file_get_contents(https://getcomposer.org/composer.phar): failed to open stream: Connection timed out
Downloading...

A quick ls on the /var/www/html dir will show that the installer file has successfully downloaded, but the error is happening when trying to run the downloaded file.

The fix is to simply remove the call to https://getcomposer.org/composer.phar inside the installer file, and then re-run it.

So, something like: sudo vi /var/www/html/installer

Then once inside vi, type in /https, and keep hitting ‘n’ until you see:

$source = (extension_loaded('openssl') ? 'https' : 'http').'://getcomposer.org/composer.phar';

Then simply edit that line to be:

$source = (extension_loaded('openssl') ? 'http' : 'http').'://getcomposer.org/composer.phar';

Yes, it’s horrible – you could do a nicer job, but this will fix it quickly and you don’t need the file after this anyway.

So once saved (:wq), you can then do:

myuser@debian-box:/var/www/html/mysite.co.uk$ sudo php installer
All settings correct for using Composer
Downloading...

Composer successfully installed to: /var/www/html/mysite.co.uk/composer.phar
Use it: php composer.phar
myuser@debian-box:/var/www/html/mysite.co.uk$ ls
composer.phar installer
myuser@debian-box:/var/www/html/mysite.co.uk$ sudo mv composer.phar /usr/local/bin/composer

Or, you know, just use Ubuntu server and not experience this kind of hassle.

VichUploaderBundle – How to Fix: Cannot Overwrite / Update Uploaded File

this picture of george osbourne has no relevance to this post. i hope it cheered you up.This one stumped me a few days back, and I have had the Chrome tab open ever since to remind myself of how to fix this in future.

The problem: VichUploaderBundle will let you upload a file, but when you go back to the form to edit / update / over-write that file by uploading a new one, it doesn’t upload the new file.

The solution: I cannot take credit for this, as the solution was found by digging around in the github closed issues list for the project, where JMather had posted his working fix.

For those who cannot be bothered to click any of the links I have so helpfully provided, read on.

Make sure you have something like this in your config.yml:

vich_uploader:
    db_driver: orm
    gaufrette: false
    storage: vich_uploader.storage.file_system
    twig: true
    mappings:
        users_uploaded_file:
            delete_on_remove: true
            delete_on_update: true
            inject_on_load: true
            upload_destination: /some/path/to/uploads
            namer: vich_user_upload_file_naming_service

I’m not using Gaufrette for a couple of reasons I won’t go in too. And also, I’m saving the given uploads outside of my Symfony install for security.

The key line is: delete_on_update: true

The other important thing here is you have to have some way of telling your ORM that the file has been updated. This is not immediately obvious, and isn’t documented at all.

So the solution seems to be:

1. Make sure you have at least an updatedAt field in the entity you are uploading too. I would normally have a createdAt and updatedAt field, and use LifecycleCallbacks to update these fields for me on prePersist and preUpdate. This isn’t well documented either in my opinion, so if you are unsure how to use them, please read my post on Lifecycle Callbacks.

2. Then update the method you are using to save the uploaded file to include an updating of the updatedAt property inside your entity. Sounds confusing, so it’s better to explain it with a code snippet:

    public function setUploadedFile($uploadedFile)
    {
        $this->uploadedFile = $uploadedFile;

        if ($uploadedFile instanceof UploadedFile) {
            $this->setUpdatedAt(new \DateTime());
        }
    }

All we are saying here is to set the updatedAt time to now if this is an uploaded file.

This then tells your ORM that the file is new – in a very round the houses kind of way – and correctly uploads the new file. And because we used delete_on_update: true, the old file is deleted so no need to worry about that.

Again, check out the github link if this still doesn’t make sense.

Thanks to Chrysweel for raising this issue, and to JMather for solving it.

Using LifecycleCallbacks for CreatedAt and UpdatedAt in Symfony 2

It's a life cycle. *Tumbleweed*

[Update: May 2015 – This post has been updated with videos]

Lifecycle Callbacks are a super helpful bit of code that I use in a good number of my entities.

They are scary sounding and the documentation on the Symfony 2 official docs doesn’t give a detailed enough demonstration (in my opinion) to explain to the new Symfony user how useful they are.

So, rather than waffle on, I will give a bit of handy code that you can steal and re-use.

namespace MCM\MyExampleBundle\Entity;

// some use statements here

/**
 * MyPretendEntity
 *
 * @ORM\Table(name="my_table_name")
 * @ORM\Entity(repositoryClass="\MCM\MyExampleBundle\Repository\MyPretendRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class MyPretendEntity
{
    // snip snip snip

    /**
     * created Time/Date
     *
     * @var \DateTime
     *
     * @ORM\Column(name="created_at", type="datetime", nullable=false)
     */
    protected $createdAt;

    /**
     * updated Time/Date
     *
     * @var \DateTime
     *
     * @ORM\Column(name="updated_at", type="datetime", nullable=false)
     */
    protected $updatedAt;

    // snip snip snip

    /**
     * Set createdAt
     *
     * @ORM\PrePersist
     */
    public function setCreatedAt()
    {
        $this->createdAt = new \DateTime();
        $this->updatedAt = new \DateTime();
    }

    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt
     *
     * @ORM\PreUpdate
     */
    public function setUpdatedAt()
    {
        $this->updatedAt = new \DateTime();
    }

    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }
}

I have seen this done other ways – some people use only one method for both.

The implementation is up to you – but do remember to use the correct annotations – @ORM\HasLifecycleCallbacks() , @ORM\PrePersist , and @ORM\PreUpdate.

There are some other events you can interact with, so be sure to check out the official docs – they become much more useful now that you understand how they work.

Input’s not editable after using Jquery Sortable

fry_coffeeJquery, I love it.

Here’s a little gotcha that I often overlook – especially when copy/pasting from the Jqueryui.com Sortable example.

If you have applied:

$( "#sortable" ).disableSelection();

Then when you finish your sorting, you won’t be able to edit any input boxes – yet check boxes and radios will still work.

The solution is mighty simple:

$( "#sortable" ).enableSelection();

Doh.

An exception has been thrown during the rendering of a template (“You have requested a non-existent service “http”

Nick-Hewer-Countdown-007I have been running v2.1.10-dev since very shortly after it was released – as I run composer update nightly.

However, I keep finding this error:


An exception has been thrown during the rendering of a template (“You have requested a non-existent service “http” *cut*

After doing an assetic:dump.

As soon as I do an assetic:dump, then a php app/console -V, my version changes from 2.1.10-dev to 2.1.4-dev.

The error above is then thrown when opening a template that uses a render(route) call inside my twig template.

It’s not major, in that running composer update resolves the issue.

For reference, I have deleted the app/cache dir with rm -rf *, as well as cache:clear. I have done this multiple times.

I have decided to remove the entire Symfony folder from vendors (sudo rm -rf vendors/symfony/*) and re-run the composer update.

Hopefully this will clear the issue.

Note: I have resolved the issue raised on the Official Symfony Blog, so if you are seeing this after updating, have Googled and found this page, then you probably haven’t corrected the issue raised on the blog article. Specifically point 6, but make sure to read it all.

And if anyone has got the same issue as I have, please let me know how you resolved it.

If I haven’t updated this post in 7 days, you can take my solution (deleting the Symfony folder from vendors) as working.

Otherwise, I am sure to be back here having a rant.

Ps, yes I am aware the post picture is irrelevant.

How to fix Method Not Found In Class orange warning in PHPStorm

little-miss-helpfulGrrr, how annoying.

My screen is full of orange, but I know these methods are valid.

Your project may be vast in scope, you may have a looming deadline for 5pm tonight, or you may just be a perfectionist who hates the thought of your IDE thinking you are making mistakes.

A good example of this is when using a foreach loop.

You have something like:

foreach( $myEntity as $entity ) {
  $entity->myValidMethod();
}

And PHPStorm says, erm, you make big mistake sir!

Now you have a valid bit of code that’s flagged up in orange as Method ‘myValidMethod’ not found in class My\Name\Space\Path\MyEntity.

Boo, hiss, and such.

It’s simple to fix though – even though it’s taken me over 100 words to get here.

Just add a comment, as such:

/* @var $entity \My\Name\Space\Path\MyEntity */
foreach( $myEntity as $entity ) {
  $entity->myValidMethod();
}

Super.

Now you no longer have a false positive.

Time to get back to meeting your deadline.

How to fix Twig Whitescreen on {{dump(var)}}

This one has been doing my head in for a while now.

Once your forms get past a certain size / complexity, the {{dump(var)}} command tends to bomb out, and throw up a rather unhelpful white screen.

Many of the results on Google tend to say – increase your PHP memory limit – never has more of an impact than taking longer to display the damn white screen.

Boo hiss, shake your first at things, etc.

Anyway, I finally found a solution.

Get your project back on track with the Ladybug Bundle.

{{ ladybug_dump(form) }}

A little of the above gives you a little of the below.

And yes, each section is expandable to give you loads of helpful detail.

example-twig-dump-ladybug-bundle

Excuse the crappy screengrab – I had to resize to get it to fit and it looks gashbag. Hey, I never said I was a designer.

Just a kick ass Symfony 2 expert 😉