Oro Quickies Moved

Just a quick, but important, site announcement.  This site, Oro Quickies, is now located at http://oro-quickies.alanstorm.com.  Updates will (and have) ceased here.  You’ll want to update your bookmarks (after crank starting your car), and/or add the feed at http://oro-quickies.alanstorm.com/rss/ to receive future updates. 

OroCRM requires PHP 5.4

I was surprised to see this when running through the new OroCRM Wizard

screenshot of PHP 5.4 requirement

It shouldn’t be a big deal — PHP 5.3 is nearing end of life after all — but it’s so common to run across a Magento shop which requires PHP 5.2 that it was a bit of a shock to see something modern. I recommend php-osx for Mac users who haven’t embraced virtual machine based development.

OroCRM and Akeneo Beta Releases

Both OroCRM/OroBAP and Akeneo (the PIM product based on OroBAP, whose developers are working closely with the OroCRM company) have reached their first betas. It is, of course, impossible to tell what anyone means by a beta anymore, but both projects seem well on their way to a 1.0.

Interestingly, they both still use the GitHub/PHP-Composer method of installation. OroCRM does have a wizard interface, but you still need to grab the code via GitHub, and dependencies via Composer. Hard to tell if this is a strategy to hold the products from general consumers until an official 1.0, or if it’s going to be The Way Things are Done™ for non-cloud based applications from now on.

Alpha 4 Installation Errors

When I was installing the new OroCRM alpha (with composer), the post-installation process bailed with the following.

Unrecognized options "use_aop, default_class" under "a2lix_translation_form"

I’m not familiar with the a2lix_translation_form bundle, but I was able to fix the problem by opening the Symfony application configuration

app/config/config.yml

and adding the following comments (yaml files use # as a commenting character; perl people, go figure)

a2lix_translation_form:
    locales: [en, fr]
    default_required: true
    templating: "OroUIBundle:Form:translateable.html.twig"
#    use_aop: false
#    default_class:
#        service: "A2lix\TranslationFormBundle\TranslationForm\DefaultTranslationForm"
#        listener: "A2lix\TranslationFormBundle\Form\EventListener\DefaultTranslationsSubscriber"
#        types:
#             translations: "A2lix\TranslationFormBundle\Form\Type\TranslationsType"
#             translationsFields: "A2lix\TranslationFormBundle\Form\Type\TranslationsFieldsType"

If this were a 1.0 release we’d probably want investigate the reasons for this a little more — but since this is still an alpha release it’s probably safe to assume things will shake out as an official release gets closer.

What’s an AbstractEntityFlexible Model Entity?

When I first started diving into the OroCRM codebase, I was a little confused by the model class declarations

class Account extends AbstractEntityFlexible
{
}

The AbstractEntityFlexible threw me. I’d heard OroBAP was using Doctrine as their ORM, but this extending the AbstractEntityFlexible made is seem like they’d implemented their own ORM. By default, Doctrine entity classes are simple classes that don’t extend anything.

[The Model], often called an “entity”, meaning a basic class that holds data – is simple and helps fulfill the business requirement of needing products in your application. This class can’t be persisted to a database yet – it’s just a simple PHP class.

Additionally, When I poked around the database I saw a multi-table-per-entity setup. This helped reenforce that assumption.

Fortunately, a bit of research pointed me right. The AbstractEntityFlexible class creates a number of standard helper methods for OroBAP objects (shortcuts for getting data, compatibility methods for twig, etc.), and nothing more.

As for the multi-table setup, this is still Doctrine. OroBAP uses a custom doctrine repository (Oro\Bundle\FlexibleEntityBundle\Entity\Repository\FlexibleEntityRepository). This appears to be what implements the multiple-table approach to storage of OroBAP model entities. I’m still getting into this system, but so far it looks like EAV-lite.  More to come as my tire kicking continues.

PHP Automatic Object Casting

This one isn’t specific to Oro or Symfony, it’s just a part of standard Object Oriented PHP.

I’m digging through Symfony’s kernel booting code, and I came across this

$cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug);
...
require_once $cache;

The variable $cache contains an object, but then it’s being used by require_once? How does that even make sense? What’s happening here is PHP’s automatic type juggling.

Small detour! In PHP, we can concatenate a string and an integer

$string = 'Foo';
$int    = 1;
$bar = $string . $int;

In other languages, this sort of thing would be illegal. The value of $string is a string, the value of $int is an integer, and concatenating something that’s not a string is not allowed. PHP, however, infers we meant to cast the $int as a string

$bar = $string . (string) $int;

In general, if you use a non-string in a string context, PHP will automatically cast the non-string to a string. So the require_once above might be more explicitly written as

require_once (string) $cache;

since the require_once function expects a string as its first parameter.

However, that still leaves the question: What should casting an object as a string do? Try something like this

$foo = new stdClass;
echo $foo,"\n";

and PHP will yell at you with an error like this.

PHP Catchable fatal error: Object of class stdClass could not be converted to string in …

All the traditionalist are nodding their head, glad PHP finally got something right. However, user defined objects have an optional magic method named __toString. Programmers may create a __toString method in their objects to define what should happen when their object is treated as a string.

That’s what’s happening in the Symfony code above. The cache object has a __toString method that looks like this

#File: vendor/symfony/symfony/src/Symfony/Component/Config/ConfigCache.php
public function __toString()
{
    return $this->file;
}

So, when you cast this object as a string, it returns the values of its file property (which is a string). A clever technique for sure, but one that’s used rarely enough it always makes me stop for 10 seconds an go “huh?” before I remember “Oh yeah, go find the __toString method”.

OroUI Built in Documentation

I’m working on my first full length Oro tutorial, and I stumbled across this tidbit. In a controller action method, try rendering the OroCRMAccountBundle::layout.html.twig template.

public function indexAction()
{
    $view = $this->render('OroUIBundle:Default:index.html.twig'); 
    return $view;
}

Load your page, and you’ll get this

Screen shot 2013-06-26 at 11.15.05 AM

A list of links with examples of the default layouts provided by the OroBAP framework. You can get the specific twig template used for a layout by looking at the OroUiBundle routing file

#File: vendor/oro/platform/src/Oro/Bundle/UIBundle/Resources/config/routing.yml
oro_ui_index:
    pattern: /
    defaults: { _controller: FrameworkBundle:Template:template, template: "OroUIBundle:Default:index.html.twig" }
...snipped...

and matching up URL patterns with templates.

Ack and Twig Templates

I’ve long advocated using the ack command line program to search through the source code of large projects. In general, it’s smart about what it should and shouldn’t search (skips .git and .svn, for example), and it provides better formatted output for matches.

However, ack won’t search .twig templates by default. You need to add them as a type. To do this, add an .ackrc file to your home directory with the following contents

--type-set=twig=.twig    

This will tell ack that .twig files are a real thing and it should search them.

Oro Backbone Navigation Cache

Speaking of the Oro.Navigation class, that’s also where the client-side ajax caching happens.

#File: vendor/oro/platform/src/Oro/Bundle/NavigationBundle/Resources/public/js/hash.navigation.js
savePageToCache: function(data) {
    if (this.contentCacheUrls.length == this.maxCachedPages) {
        this.clearPageCache(0);
    }
    var j = this.contentCacheUrls.indexOf(this.removePageStateParam(this.url));
    if (j !== -1) {
        this.clearPageCache(j);
    }
    this.contentCacheUrls.push(this.removePageStateParam(this.url));
    this.contentCache[this.contentCacheUrls.length - 1] = data;
},

Since maxCachedPages is initially set to 2

#File: vendor/oro/platform/src/Oro/Bundle/NavigationBundle/Resources/public/js/hash.navigation.js
maxCachedPages: 2,

this means OroCRM and OroBAP will cache the last two ajax navigation requests. If you want to clear that cache, you’ll need to click around. Unfortunately, because the navigation object is instantiated in a local function scope,

#File: vendor/oro/platform/src/Oro/Bundle/UIBundle/Resources/views/Default/index.html.twig
$(function() {
    //...
    new Oro.Navigation({baseUrl : 'http://oro.example.com'});
    //...
});

it’s impossible (as far as I know) to access this object and clear the cache ourselves using the methods on the Navigation object.