Categories


Archives


Recent Posts


Categories


OroCRM and the Symfony App Console

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Last time we walked you through creating a bundle in Symfony and adding it to the OroCRM application. Whenever you’re starting with a new software framework it’s always important to do this sort of step-by-step breakdown so you understand how something works. However, once you have the architecture model in your head, it can tedious, time consuming, and a general pain in the behind to create bundles manually each and every time.

Fortunately, Symfony (and therefore OroCRM/OroBAP) has a solution, and that’s the Symfony console application. This application contains a number of useful commands for developers, including code generation tools. If you’re coming from the Ruby on Rails world this is very similar to the Rails Command line.

In this tutorial you’ll learn how to create a bundle with the Symfony console application. We’ll also, by necessity, touch on Symfony’s cache folder permissions, as well as the annotations configuration syntax.

Important: The following article refers to alpha, pre-release software. While the general concepts should apply to the final version, the specific details are almost certain to change.

Running the Console

Running the Symfony console application is relatively straight forward. Just navigate to your application folder and run the following command

$ cd /path/to/crm-application
$ php app/console

Symfony version 2.1.11 - app/dev/debug

Usage:
  [options] command [arguments]

Options:
...

When you run the console command without any arguments, Symfony will output usage information, including a list of supported commands. If you take a look at the app/console file, you’ll see it’s just a simple PHP command line script

$ cat app/console
#!/usr/bin/env php
<?php    
// if you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
// umask(0000);

set_time_limit(0);

require_once __DIR__.'/bootstrap.php.cache';
require_once __DIR__.'/AppKernel.php';
//...

This application is built using the Symfony console component. If you’re familiar with the n98-magerun command line tool you’ll be right at home.

The command we’re interested in is the generate:bundle command.

$ php app/console list generate
Symfony version 2.1.11 - app/dev/debug

Usage:
  [options] command [arguments]

...options ommited...

Available commands for the "generate" namespace:
  generate:bundle              Generates a bundle
  ...

However, before we run the generate:bundle command, we need to have a quick talk about file permissions and the Symfony cache.

Symfony Cache Permissions

Like all modern web application frameworks, Symfony relies heavily on server side caching. This isn’t the “my CSS file won’t update” browser cache, but instead a server side cache where long running operations are performed once, their results stored, and then Symfony uses the stored (or “cached”) results for all future requests (until the cache is cleared).

By default, Symfony stores its cache on the file system. This is a typical setup for development environments, which means you need to be concerned about cache file permissions. This, in turn, is going to lead to a problem with the console application.

Take a look at the permissions on your cache files

$ ls -l app/cache/
total 0
drwxr-xr-x  61 alanstorm  staff  2074 Jul 12 15:53 dev
drwxr-xr-x  50 _www       staff  1700 Jul 11 13:13 prod

Your results may be different, but the above illustrates the problem perfectly. In the above example, the production cache was created by a request to the web server. This means it’s owned by the _www user. My dev cache, on the other hand, was created from running the command line console application. That means it’s owned by the alanstorm user I was logged in as when I ran the application.

So what’s the problem? By default, a cache folder created by the CLI isn’t writable by the website, and a folder created by the website isn’t writable by the CLI. If Symfony can’t write to the cache, it throws an error. Many of the initial alpha installation problems were related to this. If you take a look at the install.sh script, you’ll see it’s just a wrapper to some console calls.

#File: install.sh
#!/bin/sh
php app/console doctrine:database:create
php app/console doctrine:schema:create
php app/console oro:search:create-index
php app/console doctrine:fixture:load --no-debug --no-interaction
php app/console oro:acl:load
php app/console oro:navigation:init
php app/console assets:install web
php app/console assetic:dump

chmod -R 777 app/cache/
chmod -R 777 app/logs/

Unfortunately, there’s no one right solution here. File permissions and web applications have been clashing since the mid-90s, and I’ve yet to see a solution that manages everyone’s needs/expectations perfectly. All of which is a fancy way of saying: Here’s some options, but you’ll need to make up your own mind on the best approach.

First, the recommended Symfony approach is to use your operating system’s ACL features to ensure the web user and your own user have the same access level to the cache and logs folders. While this works, it’s not an ideal solution for me. I’m biased towards development environments that are as “unix neutral” as possible. Since I’m using a Mac to develop, that means the ACL command would be a chmod -a. However, in all likelihood we’ll be deploying to a linux machine which means the setfacl command is the one we want. That difference in environments is a mental burden I’ll need to carry for the entire project, leaving less room in my brain for more important things.

The second approach, and the one I’m using, is to have PHP’s umask set to 0000 on my local development environment. This ensures files and directories created with PHP will have 777 file permissions, which means they’ll be writable and readable by every user account on my machine. This isn’t an ideal solution, but since I’m not sharing this machine with other users it’s a compromise I can live with.

So how do you set PHP’s umask? You’ll need to edit any file that bootstraps a Symfony environment. In our case, that’s the app/console file, as well as the web/app_dev.php file (app_dev_js.php and app.php are also candidates, depending on how you’re using your development environments locally).

Open these files, and look for the call to PHP ‘umask’ function

//umask(0000);

Uncomment this line in each file, and then remove any existing cache folders

rm app/cache/dev
rm app/cache/prod
etc...

and you’ll be good to go.

As I said, debates around “the right permissions” to use for web application development are as old as time. Back in the mid-90s I had a sys-admin tell me I couldn’t set my html files to 644 because “the world will be able to see them”. You’ll need to make your own decisions as to which approach is right for you.

Running the Bundle Create Command

With that bit of sys-admin 101 out of the way, we’re ready to start. This time, we’re going to create a bundle named Helloworld2 (distinct from the Helloworld bundle we created last time). To get started, just run the generate:bundle command

$ php app/console generate:bundle

  Welcome to the Symfony2 bundle generator  

Your application code must be written in bundles. This command helps
you generate them easily.

Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle).
The namespace should begin with a "vendor" name like your company name, your
project name, or your client name, followed by one or more optional category
sub-namespaces, and it should end with the bundle name itself
(which must have Bundle as a suffix).

See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1 for more
details on bundle naming conventions.

Use / instead of \  for the namespace delimiter to avoid any problem.

Bundle namespace: 

After running the command you’ll be launched into an interactive command line experience that steps you through bundle creation. We’re going to create a Helloworld2 bundle in the top level Pulsestorm namespace, so enter Pulsestorm/Bundle/Helloworld2Bundle as the full namespace.

Bundle namespace: Pulsestorm/Bundle/Helloworld2Bundle
In your code, a bundle is often referenced by its name. It can be the
concatenation of all namespace parts but it's really up to you to come
up with a unique name (a good practice is to start with the vendor name).
Based on the namespace, we suggest PulsestormHelloworld2Bundle.

Bundle name [PulsestormHelloworld2Bundle]:

Next, Symfony is telling us we need to name our bundle. We didn’t do this last time — we just stuck with the default. While it’s possible to have your bundle named differently, in practice this may cause confusion, so let’s just go with the default (PulsestormHelloworld2Bundle) again

Bundle name [PulsestormHelloworld2Bundle]: 

The bundle can be generated anywhere. The suggested default directory uses
the standard conventions.

Target directory [/path/to/crm-application/src]: 

The console application is asking us where we want to create our bundle. Again, the default will be fine here. Remember, the whole point of bundles is they group all our code and resources together, meaning they can be easily moved around later.

Target directory [/path/to/crm-application/src]: 

Determine the format to use for the generated configuration.

Configuration format (yml, xml, php, or annotation) [annotation]: 

Here symfony is asking us what format we’d like our bundle’s configuration to be in. In this case, we’re going to change this from the default (annotation) to YAML (yml). We’ll talk more about why below, but for now just sit tight.

Configuration format (yml, xml, php, or annotation) [annotation]: yml

To help you get started faster, the command can generate some
code snippets for you.

Do you want to generate the whole directory structure [no]? 

Here Symfony is asking if we’d like a full generation of code, or a minimal one. Let’s say yes to the whole directory structure.

Do you want to generate the whole directory structure [no]? yes

  Summary before generation  

You are going to generate a "Pulsestorm\Bundle\Helloworld2Bundle\PulsestormHelloworld2Bundle" bundle
in "/path/to/crm-application/src/" using the "yml" format.

Do you confirm generation [yes]?

Before creating your bundle, the console application will confirm what, exactly, it’s going to do. Review the information, and press enter to continue.

Do you confirm generation [yes]?                     

  Bundle generation  

Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]?

Symfony will confirm the bundle’s been generated. At this point, your bundle code has all been generated in the src directory. Next, Symfony asks if you’d like to automatically update the AppKernel file. Specifically, this refers to updating the registerBundles method with a bundle declaration. Hit enter to say yes.

Confirm automatic update of your Kernel [yes]?
Enabling the bundle inside the Kernel: OK

Confirm automatic update of the Routing [yes]? yes

After confirming it was able to update the app/AppKernel.php file, Symfony will ask if you want to update the bundle’s routing information. Saying yes to this will automatically add a configuration entry to the app/etc/routing.yml file. This configuration entry will point to your bundle’s routing.yml file, and enable routing for your bundle. Hit enter to say yes, and bundle creation will be complete.

Importing the bundle routing resource: OK

  You can now start using the generated code!  

To review, after running the generate:bundle command we’ll have

Congratulations, you just created your first automated bundle. As you can see, this is much less tedious than manually creating individual files. Before we wrap up, let’s get to the caveats we brushed aside earlier.

Updating the AppKernel File

After finishing the bundle:generate command, try to get a list of commands from the console application. If you’re unlucky, you may run into the following error

$ php app/console list
PHP Parse error:  syntax error, unexpected ',', expecting ')' in /path/to/crm-application/app/AppKernel.php on line 61
PHP Stack trace:
PHP   1. {main}() /path/to/crm-application/app/console:0

The console application uses the same PHP bootstrapping process as the main OroCRM application. Unfortunately, while relatively clever, the bundle:generate‘s AppKernel updating code isn’t bullet proof. If you take a look at the updated AppKernel file at the line specified

#File: crm-application/app/AppKernel.php

            new OroCRM\Bundle\DashboardBundle\OroCRMDashboardBundle(),
,
            new Pulsestorm\Bundle\Helloworld2Bundle\PulsestormHelloworld2Bundle(),

you’ll quickly see the problem. Symfony’s updating code assumes the main $bundle array does not end in a comma, so it inserts one for you — even if this make the file invalid PHP code. You’ll need to manually remove the extraneous comma.

new OroCRM\Bundle\DashboardBundle\OroCRMDashboardBundle(),
new Pulsestorm\Bundle\Helloworld2Bundle\PulsestormHelloworld2Bundle(

Once you remove the comma, you should be good to go.

Bundle Configuration

One of the bundle creation commands we skipped the defaults on was the following.

Determine the format to use for the generated configuration.    
Configuration format (yml, xml, php, or annotation) [annotation]: 

This one’s going to require a bit of explanation. You don’t need to understand this fully to continue, so if your head’s starting to spin just let the information soak in for a bit.

In addition to the AppKernel class, Symfony’s (and therefor Oro’s) architecture philosophy is service oriented dependency injection. Wait! Come back! Dependency injection seems like a fancy complicated term, but at its core it’s pretty simple. Dependency injection allows a programmer to “easily” (i.e. through configuration) swap out the instantiation of one object for another.

In Drupal the concept is called pluggable — the Drupal core team explicitly sets certain classes such that they could swapped out for others via configuration. In Magento, its called class rewrites. With Magento, all models, blocks, and helper objects were made dependency injectable (assuming they use Magento’s factory instantiation methods)

Symfony’s system splits the difference between these two. In Symfony, there’s a special kind of object called a “Service”. This isn’t a web-service like SOAP or REST. A service is simply an object that does something for you, that you (or someone else) might want to change the implementation of.

So, services require configuration, and that’s what Symfony is asking about above. By choosing yml, we told Symfony to create the following service configuration file

src/Pulsestorm/Bundle/Helloworld2Bundle/Resources/config/services.yml 

which in turn is loaded by the special dependency injection class at

src/Pulsestorm/Bundle/Helloworld2Bundle/DependencyInjection/PulsestormHelloworld2Extension.php 

We’ll eventually go deeper on how Symfony knows to look for these files. For now just accept it as magic (or dive into the kernel’s handle method on your own, although reading too far ahead may melt your brain).

So far so good. While conceptually this may be new to you, it’s relatively straight forward. If you were yearning for something more complex, don’t worry, we’re about to ruin that simplicity.

When is Configuration not Configuration

In addition to service configuration, Symfony also has configuration for its MVC/URL routing. Philosophically, Symfony views service configuration as an entirely different thing than the routing configuration. These are separate systems — that’s why service configuration goes in service.(yml|xml|php), and routing configuration goes in routing.(yml|xml|php).

However, the borders between the two systems aren’t as clear as you’d think. When we answered the configuration question

Configuration format (yml, xml, php, or annotation) [annotation]: yml

We were telling our bundle to use the yml format for both routing and service configuration. Not that big a deal, until we consider the default annotation configuration format.

What is the annotation format? Unlike yml, xml, and php, the annotation format does not rely on external configuration files. Instead, a special syntax is used in PHP doc-blocks. You can see an example of a routing annotation in the Symfony manual

/**
 * @Route("/{id}", requirements={"id" = "\d+"})
 */
public function showAction($id = 1)
{
}

and some examples of dependency injection configuration in this file.

Covering annotations is beyond the scope of this article, and I’d recommend you stay away from them when you’re starting Symfony development. They’re conceptually interesting — but it’s also a step beyond normal configuration. That is, learning to configure Symfony’s routes and services is already a tricky thing if you’re never done it before, and annotations will only muddy the waters further.

For example, despite choosing annotation as the format, the generation code still creates a service configuration file. If we had chosen annotation, the PulsestormHelloworld2Extension object would still load a service.xml file. Even though we chose annotation as the format, Symfony still created an XML configuration file. Also, the Doctrine ORM system (used by OroBAP/Symfony) uses annotations as well, but these annotation have nothing to do with routing or service configuration.

As you become conversant in Symfony’s dependency injection and routing features, you’ll start to understand the problem annotations are trying to solve. If you’re just getting stared through, it’s headache inducing. Once you have basic Symfony configuration under your belt then consider taking a look at annotations. We only mention them here so you’ll know it’s ok to avoid them for now, but that you’ll recognize them when you see them in other people’s code.

Wrap Up

Today we covered using the Symfony console application to automate bundle creation. This is only one of the many tasks the application console can help you with — think of it as your Symfony swiss army knife.

In exploring full bundle creation, we (unavoidably) stumbled head first into the deeper world os Symfony’s service container architecture. In our next article, we’ll cover the basics of service configuration, and why we’ll want to go to the extra trouble for our Oro applications.

Originally published July 21, 2013
Series Navigation<< OroCRM Hello WorldOroCRM Frontend Asset Pipeline >>