jeffturcotte
1/15/2013 - 7:25 PM

Builder.php

<?php
/**
 * A class to help with adding build methods
 * to your fActiveRecord models.
 *
 * @author  Jeff Turcotte <jeff.turcotte@gmail.com>
 * @license MIT
 * @version 1.0
 *
 * Usage:
 *
 *    $builder = Builder::create('Page');
 *
 *    $builder->setDefaultOrder(array('name' => 'asc'));
 *
 *    $builder->register('buildActive', function(&$where) {
 *        $where['status='] = 'active';
 *    });
 *
 *    $builder->extend('buildActive', 'buildActivePublished', function(&$where) {
 *        $where['published='] = TRUE;
 *    });
 * 
 *    Page::buildActivePublished(10, 1);
 * 
 */
class Builder {
	static protected $registry;

	protected $callbacks;
	protected $class;
	protected $default_order;
	
	/**
	 * Create a Builder based off of the class name.
	 * If a Builder for the supplied class exists, it will
	 * return that.
	 *
	 * @param $class string  The fActiveRecord class to use
	 *
	 * @return Builder
	 */
	public function create($class) {
		if (isset(self::$registry[$class])) {
			return $registry[$class];
		}	

		$instance               = new self();
		$instance->callbacks    = array();
		$instance->class        = $class;
		self::$registry[$class] = $instance;

		return $instance;
	}

	/**
	 * Set a default order by clause for all build
	 * methods. Can be altered by the registered build
	 * methods if necessary.
	 *
	 * @param $order Array  The order by 
	 *
	 * @return void
	 */
	public function setDefaultOrder($order)
	{
		$this->default_order = (array) $order;
	}


	/**
	 * Register a build method on the record
	 *
	 * The callback can take four references:
	 *    &$where  The where clauses to modify
	 *    &$order  The order by clauses to modify
	 *    &$limit  The limit to modify
	 *    &$page   The page to modify
	 *
	 * @param $method string      The method name to register
	 * @param $callback callable  The builder callback, see method desc.
	 *
	 * @return void
	 */
	public function register($method, $callback=NULL)
	{
		$this->callbacks[$method] = $callback;

		fORM::registerActiveRecordStaticMethod(
			$this->class, $method, $this->makeBuilder($callback)
		);
	}

	/**
	 * Extends an existing build method.
	 *
	 * @param $parent string      The existing method name to extend
	 * @param $method string      The new method name to register
	 * @param $callback callable  The builder callback, see register() docs for desc.
	 *
	 * @return void
	 */
	public function extend($parent, $method, $callback=NULL)
	{
		$parent_callback = $this->callbacks[$parent];

		$this->register($method, function(&$where, &$order, &$limit, &$page) use ($parent_callback, $callback) {
			$parent_callback($where, $order, $limit, $page);
			$callback($where, $order, $limit, $page);
		});
	}

	/**
	 * Makes a builder callback to register on fActiveRecord
	 *
	 * This creates an fActiveRecord method that takes the following parameters:
	 *
	 *    $limit
	 *        The custom limit for the returned fRecordSet
	 *    $page
	 *        The custom page to limit to
	 *    $order
	 *        The custom order array for the fRecordSet
	 *    $inline_callback
	 *        A callback in the same form as when registering a builder.
	 *        Can be in any argument position, as long as it's last.
	 *        For inline extensions, such as search.
	 *
	 * @param $callback callable  The builder callback, see register() docs for desc
	 *
	 * @return Closure
	 */
	protected function makeBuilder($callback=NULL)
	{
		$default_order = $this->default_order;

		return function($class, $method, $args) use ($callback, &$default_order) {
			$where = array();
			$order = array();
			$limit = NULL;
			$page  = NULL;
			$inline_callback = NULL;
			
			$arg_names = array(
				'limit',
				'page',
				'order',
				'inline_callback'
			);

			foreach($arg_names as $key => $value) {
				if (isset($args[$key])) {
					if (count($args)-1 == $key && is_callable($args[$key])) {
						$inline_callback = $args[$key];
						break;
					}
					$$value = $args[$key];
				}
			}

			call_user_func_array($callback, array(
				&$where, &$order, &$limit, &$page
			));

			if ($inline_callback) {
				call_user_func_array($inline_callback, array(
					&$where, &$order, &$limit, &$page
				));
			}

			foreach($default_order as $column => $dir) {
				if (!isset($order[$column])) {
					$order[$column] = $dir; 
				}
			}
			
			return fRecordSet::build(
				$class, $where, $order, $limit, $page
			);
		};
	}
}