Meet Hadori. She’s beautiful, magnificient, and everything you’ve ever dreamed her to be. Go head, don’t be shy… click on her!. You can also browse the fully-functional demo, if you’re into that kind of thing.

List View
Hadori Screenshot for the List View
Filters Editing
Hadori Screen Shot for Filters Hadori Screenshot for the Edit Action
Detail View (show) Exporting (yeah!)
Hadori Screenshot for the Show Action Hadori Screenshot for the Export Action

Why Switch?

  1. Export models as CSV
  2. Generated code that doesn’t strangle you
  3. Full test coverage
  4. Finally… a show action!
  5. Support for sortable models
  6. Gorgeous Redmine-style filtering
  7. Optional internationalization support
  8. Integration with security.yml settings
  9. Semantic markup, semantic CSS, and a beautiful theme!
  10. Damn good documentation

The primary purpose of Hadori is to provide you with meaningful, generated code. The purpose of an admin generator is to get you started with basic functionality that every administrator needs. However, your app’s requirements will eventually outgrow the scope of any configurable generator. When this happens, Hadori ensures you hit the ground running.

The generated code in Hadori isn’t good. It’s gorgeous. Beautiful. Hadori writes code better than your mom, guaranteed. Check it out.

Below is a comparison between symfony’s built-in admin generator and Hadori. This has been done using the out-of-the-box configuration for the sfGuardUser model. You can quickly see how much more lightweight hadori is, but more importantly you can see how much easier it is to understand the code itself.

Hadori Symfony’s Admin Gen compare
list header partial 25 lines 161 lines compare
form rendering 1 partial 4 partials compare
generated configuration class none! 293 files compare
filesystem 14 files 29 files compare
process form action 15 lines 42 lines compare
batch action 13 lines 44 lines compare
generated helper class none! 18 files compare

So what next?

  1. Check out the fully-functional demo
  2. Read the awesome documentation
  3. View a sample generator.yml from the demo
  4. Poke around the demo’s generated code
  5. Once you’re convinced Hadori is the plugin for you, follow the Installation and Setup instructions on github
  6. As always, don’t hesitate to contact me with any questions!

note: I have every intention of using as much of these concepts as possible in the Sonata AdminBundle for Symfony2. Stay tuned!

A while ago, I wrote a post entitled “Faux Form Serialization“. It was an overly-complex solution to a very easy problem. Today, I am writing about a much better implementation for solving the same problem.

What is this concept? You’ve heard of MongoDB right? CouchDB? BeullerDB…? Of course you have. You’ve been tired of writing migrations to add and remove columns that are constantly changing, and at some point have had the thought

“By golly, I should just slap an options field on this honkey!”

You’re exactly right, and this is what you should do.

Doctrine already has a lovely array field type. Similarly, it serializes an array into a longtext database column and unserializes upon hydration. Well that sounds useful!

Exposing these fields can be tricky though. We have this lovely form framework with all its pretty widgets and validators, and the ability to embed them! Yet, since this isn’t a true related object, we can’t do this out of the box… or can we?!

Example Time

Your sfGuardUser object has a laundry list of silly auxiliary fields, including items such as “how often do you zumba?” and “what animal produces your favorite milk?”. You refuse to write migrations for these things. Instead, you’ll save them all to a single field of type array called extra_profile_fields. Good for you! Create a form:

// lib/form/sfGuardUserExtraProfileFieldsForm.class.php
class sfGuardUserExtraProfileFieldsForm extends BaseForm
{
   public function configure()
   {
      $this->setWidgets(array(
        'zumba_frequency' => new sfWidgetFormChoice(array('choices' => array('Not at all', 'Sometimes', 'Every Single Day', 'I NEVER STOP'))),
        'favorite_animal_milk' => new sfWidgetFormInput(),
      ));

      $this->setValidators(array(
        'zumba_frequency' => new sfValidatorChoice(...),
        'favorite_animal_milk' => new sfValidatorString(...),
      ));
   }
}

Now, use my very special sfWidgetFormEmbeddedForm and sfValidatorEmbeddedForm

// lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends PluginsfGuardUserForm
{
    public function configure()
    {
       ....
       $form = new sfGuardUserExtraProfileFieldsForm();
       $this->widgetSchema['extra_profile_fields']    = new sfWidgetFormEmbeddedForm(array('form' => $form));
       $this->validatorSchema['extra_profile_fields'] = new sfValidatorEmbeddedForm(array('form' => $form));
    }

Wow, is it that easy? YES IT IS. The form will render expectedly, with validators and all. However, make sure you pass the same form object to both the widget and the validator! If you, for instance, pass a new sfGuardUserExtraProfileFieldsForm instance to each, you’ll have two separate forms, and errors in your form will not be rendered.

Extra Fun: Using Archivers

Let’s say you wanted the options to save as YAML, JSON, or a PHP-serialized string. As long as you use the “array” column type in doctrine, none of this is necessary. But if you need this flexibility, I’ve got your back.

The ArchiverInterface specifies a class with three methods: sleep, wake, and isAsleep. Essentially, it will convert from a certain array to a format and back again. Here are some sample archivers for your pleasure alone. Pass the class name as the “archiver” option to widget and validator, and you’re all set!

Hopefully you guys will enjoy this and use this as much as I have. And please don’t hesitate to submit comments, suggestions, and questions.

You all should be testing your plugins. You know that, right?

Regardless of whether or not you know that, chances are you have NO EARTHLY IDEA how to accomplish it. I’ve spent a while getting tests up and running for the release of my plugin sfThemeGeneratorPlugin. The truth is, the native testing framework is filled with hacks. This post illustrates how to compound upon these hacks so they do your bidding. We will use the Lime testing framework bundled with symfony.

Methodology

The Why:
First of all, why test your plugins? Plugin tests ensure you won’t break your buddy’s project when you push minor fixes. Also, it gives your buddy this same confidence when upgrading to a new release. Plugins are self contained libraries. They should be able to be tested independently of a particular symfony project. In fact, they should be able to run with any symfony library you provide to them, granted the release is supported by your plugin. A plugin’s test should be independent of every dependency it has. Symfony is a dependent library, as are any plugins required for your plugin to run.

With the ability to define the paths to your plugin’s dependencies, you can quickly test your plugin across multiple releases of those dependencies. It also allows your plugin’s users to run your tests within the context of their application. Who has two thumbs and an awesome plugin? You do my friend. You do.

The How:
Our plugin should contain an executable file to run its tests. The executable should accept parameters for the paths to any of its dependencies. The plugin in my example accepts the symfony_dir argument like this:

$ cd sfMyAwesomePlugin/test/bin/
$ php prove.php
Symfony directory not found.  Please set $_SERVER['SYMFONY'] or provide a --symfony_dir argument
$ php prove.php --symfony_dir=/usr/local/lib/symfony/RELEASE_1_4_0/lib
plugins/sfMyAwesomePlugin/test/functional/sfMyAwesomePluginFunctionalTest..ok
plugins/sfMyAwesomePlugin/test/unit/sfMyAwesomePluginUnitTest........ok
All tests successful. Files=2, Tests=12

At the same time, we want to be able to run our tests as a part of our application test suite. Luckily, this part is feasible through the existing Symfony test framework.

# Task to run all tests in your plugin
$ php symfony test:plugin sfMyAwesomePlugin

The Process

So how do we do it? First, we need to include a symfony project fixture in the plugin. This is required for both unit and functional tests. We’ll add this in test/fixtures/project. Now before you post a picture of Xsibit, let me explain why this isn’t a terrible idea. Even though the symfony framework is rather robust, you can pear it down quite a bit. Take it down to bare bones and you are left with only 6 of files and 10 directories. This is the minimum directory structure needed to run a symfony project fixture:

# symfony project fixture
/path/to/plugin/fixtures/project
    /apps
         /frontend
             /config
                 /frontendConfiguration.class.php
                 /routing.yml
             /lib
                 /myUser.class.php
    /cache
    /config
        /doctrine*
            /schema.yml*
        /databases.yml*
        /ProjectConfiguration.class.php
    /data
        /fresh_test_db.sqlite*
    /lib
    /log
    /web
        /index.php

The files and folders with asterisks(*) next to them are only needed if you’re using a the database layer. For this, I strongly suggest using sqlite. We will copy the fresh database over each time the tests run. Build your test database by running the DB build task in the fixture project (For doctrine and symfony 1.3+ you would use doctrine:build --all).

You can download the symfony project fixture here. Now that we have a project fixture, we need to add our execution binary. We’ll use a prove.php file. This is a simple PHP execution file, and does nothing but include your bootstrap file and run your tests. This is also where we want to check to make sure the path to your symfony library is specified. Add this file to /path/to/plugin/test/bin/prove.php

Next we want to have a Bootstrap class. I took the liberty to make the sfPluginTestBootstrap class. I refuse to write a single line of PHP procedural code that is not strictly necessary. This file will determine the symfony directory, autoload symfony core, and register the setup and teardown functions. This class also begs you to extend it for your project if needed. Add this file to /path/to/plugin/test/bootstrap/sfPluginTestBootstrap.class.php

Finally, we need to have a bootstrap file. This is similar to the bootstrap you’ll find in any of your project’s tests. While I am no more a fan of this than you, this is the way things work with Lime. Mine looks like this: bootstrap.php. This will be the sole include at the top of each test. This will allow you to run each test manually via command line ($php sfMyAwesomePluginTest.php) and via the prove.php file. Wootness. Add this file to /path/to/plugin/test/bootstrap/bootstrap.php

The Result

When you get to this point, you should have something like this in your plugin’s test directory: Sample Plugin Test Directory

So what do we need now? The good news is… that’s pretty much it! Now we just need to add the test files themselves. These will look exactly like a regular symfony test. That’s right, you can write a unit or functional test in whatever project you’re using, and just move the files into your plugin. You may need to adjust the bootstrap include at the top of each test, but this approach follows the symfony convention, so even that is unlikely. That’s nice, right?

Now sit back and enjoy all the benefits that come with having beautiful test coverage. Confidently notate compatible symfony versions. Give your users the piece of mind knowing your plugin is compatible with their application. And refactor with confidence! I basically had to cage fight a rabid bear to figure all this out. Now all you need to do is follow these steps. Give my wounds purpose, and test your plugins.

You should be testing your applications. End of story. This post outlines a task I created that makes testing your application easier. This test is great if you fall into one of the following categories:

  1. You want to run a group of test files you’ve organized into a directory.
  2. You want to run a group of test files based on a word or words they have in common
  3. You want one command line task for all the tests you run, rather than having to type unit or functional and the application every time. Shouldn’t the task be smart enough to run individual files?

These reasons are why I wrote sfTestFileTask.class.php. I have pompously given this task the alias “test” so you can run it like this:

# runs all tests in your test suite
$ php symfony test

To run a group of test files you’ve organized into a directory:

# runs all tests in the directory /path/to/project/test/functional/backend/export
$ php symfony test export

To run a group of test files based on a word or words they have in common:

# runs all tests starting with "User"
$ php symfony test User*
# runs all tests containing the word "content"
$ php symfony test *content*

To run any test file:

# runs the test found in test/functional/backend/export/OrganizationExportTest.php
$ php symfony test OrganizationExport
# task is smart enough to ignore requests ending in "Test"
$ php symfony test OrganizationExportTest
# runs test in "verbose" mode, in order to view errors
$ php symfony test OrganizationExport -v

All these tests run as you see when using the test:all command. Providing the -v command runs all tests as if they are ran individually with test:functional or test:unit.

Place the code in lib/task/sfTestFileTask.class.php and you’ll be all set!

Please let me know if you have any ideas for improvement.

Recently I created a model I called sfDoctrineSecurityGenerator. It’s an awesome class that you can drop directly into generator.yml that matches the admin generator’s security credentials with those that you set in security.yml. This works with the default admin generator in core, and has worked swimmingly for me.

So what is sfModelGenerator? sfModelGenerator is an abstract class extended by the ORM in order to auto-generate files. This class is used anywhere you have a generator.yml file, or any time you run the tasks ./symfony doctrine:generate-module or ./symfony doctrine:generate-admin.

Step 0 (optional): If you don’t have a module set up that uses the generator.yml file, see Fabien’s Screencast on how to use it. You can also run a command like this: php symfony doctrine:generate-admin myapp MyModel.

Step 1: Add this file to /path/to/project/lib/generator/sfDoctrineSecurityGenerator.yml.

Step 2: Swap out the sfDoctrineGenerator field in your generator.yml for sfDoctrineSecurityGenerator. Should look like this:

generator:
  class:                            sfDoctrineSecurityGenerator      # was "sfDoctrineGenerator"
  param:
    model_class:                     MyModel
    theme:                              default
    ...

Step 3: Add a security.yml file to your module’s config directory if one doesn’t exist already. This would look something like below:

# /path/to/project/myapp/modules/company/config/security.yml
all:
  is_secure: on

edit:
  credentials: [Company Edit]

new:
  credentials: [Company New]

delete:
  credentials: [Company Delete]

Step 4: Set the use_security_yaml_credentials flag to true.

Add use_security_yaml_credentials: true to your generator.yml’s params, so it looks like the following:

generator:
  class: sfDoctrineSecurityGenerator
  param:
    model_class:           Company
    ...
    use_security_yaml_credentials: true

    config:
      actions: ~
      fields:  ~
      list:    ~
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~
Note: If you don’t set up a security.yml file (Step 3), this next step will throw an exception

You are now in business. The admin generator is now smart enough to hide EDIT links from users lacking the Company Edit credential.

This is the same with DELETE and NEW actions as well. In fact, this works for any custom action declared in generator.yml. For instance, if your generator.yml looked something like…

...
  config:
    actions:
      mycustomaction: { name: 'Do Stuff Now!', action: dostuff }
...

…and a dostuff credential existed in security.yml, the link “Do Stuff Now!” will only show to those with the correct credentials. Pretty cool stuff.

The sfDoctrineSecurityGenerator.yml class can be found here. As always, please report any issues and give me your feedback!