Conditional Validation in Symfony Forms

March 9th, 2010 nick No comments

I recently had call to create a form that performed validation on some of it’s fields conditionally, requiring one set of fields if a toggle was marked, another set if not.  After getting it all set up I thought some of you out there might find it useful (and I’m pretty sure I’m going to come looking for this again eventually) so here you go.

First I registered a post callback validator with a new function I created in my form class:

public function configure() {
  ...

  $this->mergePostValidator(new sfValidatorCallback(array('callback' => array($this, 'checkConditionalRequirements'))));
}

Then in my new function I create a new error schema and re-run validation on all fields that I determine should be required based on the current data:

  public function checkConditionalRequirements($validator, $values, $arguments) {
    // container for errors that arise
    $errorSchema = new sfValidatorErrorSchema($validator);
    // list of required fields that we're building
    $required = array();

    // only require a password on new objects
    if($this->getObject()->isNew()) {
      array_push($required, 'password');
    }

    // register appropriate required fields based on current form context
    if($values['condition_field'] == '1') {
      array_push($required, 'field_one', 'field_two');
    }
    else {
      array_push($required, 'field_three');
    }

    // re-process validation on all marked fields
    foreach($required as $field) {
      $this->validatorSchema[$field]->setOption('required', true);
      try {
        $this->validatorSchema[$field]->clean($values[$field]);
      }
      catch(sfValidatorErrorSchema $e) {
        $errorSchema->addErrors($e);
      }
      catch(sfValidatorError $e) {
        $errorSchema->addError($e, $field);
      }
    }

    // if something went wrong let's return it to the currently running validation process in a form it will understand
    if(count($errorSchema)) {
      throw $errorSchema;
    }

    // return clean values
    return $values;
  }

That’s it, and it can be easily extended to re-run validation against more than just requirements.

Categories: Symfony Tags:

Integrating Symfony and WordPress

December 2nd, 2009 nick 19 comments

First off I want to thank this post for getting me started on this project, it saved me a lot of time and most of the steps here are just adjustments to their implementation.

This guide will be geared toward symfony 1.2 and WordPress 2.8+.  Earlier versions of WordPress should integrate in relatively the same way but earlier versions of Symfony may cause complications.

The Implementation: This integration works by creating a custom web controller that pre-loads all of the WordPress functionality alongside Symfony.  Next we dedicate a module to pass-through all of the WP requests which then uses output buffering to capture the WordPress rendering of the request without a WordPress theme and places it within the Symfony application’s layout.

The Result: Have all of the functionality and extensibility of WordPress within a Symfony application, rendered in your Symfony application’s layout for consistency and having access to the functionality from both platforms.

Step 1: Install WordPress

Download the latest WordPress and extract it into the web folder that you’ll be serving it out of.  Integrating an existing WordPress installation?  Just copy the entire installation’s containing folder into the web directory of your Symfony app.  For the purposes of this guide I’ll be setting it up in /blog/ so after this step I’ll have:

web/blog/index.php
web/blog/license.txt
web/blog/readme.html
...

Step 2: Create a Dedicated Web Controller

Copy your application’s production and dev web controllers (for this example I’m using an application called ‘frontend’ so my controllers would be index.php and frontend_dev.php) to wordpress.php and wordpress_dev.php respectively.  In each of these new files add the following two lines just after loading the application configuration:

define('WP_USE_THEMES', true);
require_once(sfConfig::get('sf_web_dir') . '/blog/wp-load.php');

For reference, my complete production controller becomes:

<?php

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
define('WP_USE_THEMES', true);
require_once(sfConfig::get('sf_web_dir') . '/blog/wp-load.php');
sfContext::createInstance($configuration)->dispatch();

Add URL redirection for your controller by adding the following to your web root’s .htaccess file before your index.php controller redirection (line 15 of the default Symfony .htaccess file) :

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^blog/(.*)$ wordpress.php [QSA,L]

Step 3: Create the Module

Create a new module in your application:

./symfony init-module frontend wordpress

Adjust the routing.yml (apps/frontend/config/routing.yml) to include the following:

blog:
  url:   /blog/*
  param: { module: wordpress, action: index }

Change your index action (apps/frontend/modules/wordpress/actions/actions.class.php) to be the following:

	public function executeIndex(sfWebRequest $request) {
		ob_start();
		wp();
		require_once( ABSPATH . WPINC . '/template-loader.php' );
		if (function_exists('is_feed') && is_feed()) {
			ob_end_flush();
			throw new sfStopException();
		}
		else {
			$this->blog = ob_get_clean();
		}
	}

Set your index template (apps/frontend/modules/wordpress/templates/indexSuccess.php) to include:

<?php echo $blog ?>

Step 4: Resolving Conflicts

Right now we have everything we need to let both of the systems work together except for one very large issue: both Symfony and WordPress define and use the functions esc_js() and __().  To resolve this I chose to rename WordPress’s versions to wp_esc_js and wp__ respectively.  On a Unix system the following shell commands can be run from the top level of your wordpress installation directory to rename all instances of these functions:

find . -name *.php -print | xargs sed -i 's/\(\b\)esc_js\(\b\)/\1wp_esc_js\2/g'
find . -name *.php -print | xargs sed -i 's/\(\b\)__\(\b\)/\1wp__\2/g'

Some systems will return an error from find after running the above, the solution for this is to enclose your search pattern in quotes:

find . -name "*.php" -print | xargs sed -i 's/\(\b\)esc_js\(\b\)/\1wp_esc_js\2/g'
find . -name "*.php" -print | xargs sed -i 's/\(\b\)__\(\b\)/\1wp__\2/g'

Run this as many times as you’d like on the same files without incident.

In the comments Ka has provided the following for the above on OSX:

find . -name ‘*.php’ | xargs perl -pi -e ’s/(\b)__(\b)/\1wp__\2/g’

Step 5: The Layout

WordPress is still rendering all of it’s content in the configured WordPress theme and then that is all being rendered in your Symfony layout.  One option here is to empty your theme’s header.php, footer.php and sidebar.php files. Another approach involves removing all calls to your theme’s get_header(), get_footer() and get_sidebar() functions, the following list should encompass all of the files you’ll need to change:

404.php
archive.php
archives.php
image.php
index.php
links.php
page.php
search.php
single.php

The good news is that you have access to all of these functions from within your Symfony application’s layout.  For example, to include the sidebar back in to your blog pages simply add the following to your current Symfony layout:

<?php if(function_exists('get_sidebar')) { get_sidebar(); } ?>

Step 6: Wrapping Up

Run through the WordPress guided installation if you haven’t already. When you’re finished and logged in you’ll notice that redirecting to the admin homepage after logging in gives you a Symfony 404.  This can be corrected by adding the following to your .htaccess file, replacing ‘blog’ with your WordPress installation directory:

RewriteRule ^blog/wp-admin/$ blog/wp-admin/index.php

Step 7: A Model

Run the following to generate a schema from the new WordPress tables:

Propel:

./symfony propel:build-schema

Doctrine:

./symfony doctrine:build-schema

To get proper export and import through fixtures files be sure to add the table references in to the generated schema, they won’t be generated from the previous commands.  As of right now this includes:

wp_postmeta.post_id
wp_term_relationships.object_id
wp_term_relationships.term_taxonomy_id
wp_term_taxonomy.term_id
wp_usermeta.user_id

Another note: I can only attest to this issue occurring in a Propel setup but there are 3 primary keys on the wp_options table and Propel doesn’t seem to be able to determine a proper identifier for these objects when generating fixtures so they all overwrite each other until you only end up with one object.  To correct this I remove the primaryKey:true attribute on everything but the option_id column for this table and I suggest you do the same.

Enjoy

That’s it, you’re all set.  If you have any comments or questions or if you found this guide useful please feel free to leave a comment below.

In The Future

Eventually you’re going to want to upgrade WordPress to another version.  After you’ve installed the upgrade re-apply steps 4 and 7 and you’ll be good to go.

My thanks to Ka and Paulo for their improvements to this guide.

Update: After playing with the Trackback piece of WordPress it seems that this feature needs some tweaking to work with the set up above.  To get trackback support again add the following to the top of the ‘wp-trackback.php’ in your WordPress installation directory:

global $wp, $wpdb, $posts, $wp_query;
Categories: Symfony, WordPress Tags: