Skip to content

Loading…

Lazily initialize Model's metadata, refactor Model::_init() and Model::_classes is no more static #608

Closed
wants to merge 1 commit into from

1 participant

@jails
Union of RAD member

Some refactoring of the Model class :

1 - Model's metadatas can be initialized using cutom closures.

Posts::config(array(
    'initializers' => array(
        'source' => function(&$meta) {
            if ($meta['source'] === null) {
                $meta['source'] = 'custom_posts';
            }
        },
        'custom' => function(&$meta) {
            $meta['custom'] = 'custom_data';
        }
    )
));

2 - Model::_classes is no more static.

3 - Since Models are in the "same time" static and dynamic, for consistency, this PR reintroduce the _init() method which is a standard method called just after __construct.
Here _init() is called just after the class instanciation (instead of __construct) and can be used for lazy initializations.
__init() can always be used for "direct" initializations.

PS:
__init() is a static function called during the autoload step. using parent::__init() will result to call the parent's __init() twice. So in li3 parent::__init() is an invalid syntax and shouldn't be used.

@jails jails closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 9, 2012
  1. @jails

    Lazily initialize Model's metadata, refactor Model::_init() and Model…

    jaillet committed with jails
    …::_classes is no more static
Showing with 217 additions and 20 deletions.
  1. +92 −19 data/Model.php
  2. +68 −1 tests/cases/data/ModelTest.php
  3. +57 −0 tests/mocks/data/MockPostFiltered.php
View
111 data/Model.php
@@ -8,6 +8,7 @@
namespace lithium\data;
+use lithium\core\Libraries;
use lithium\util\Set;
use lithium\util\Inflector;
use lithium\core\ConfigException;
@@ -132,11 +133,19 @@ class Model extends \lithium\core\StaticObject {
protected static $_instances = array();
/**
- * List of initialized instances.
+ * If true, the initializer method `_init()` will be called
*
* @see lithium\data\Model::_init();
* @var array
*/
+ protected $_init = true;
+
+ /**
+ * List of initialized instances.
+ *
+ * @see lithium\data\Model::_initialize();
+ * @var array
+ */
protected static $_initialized = array();
/**
@@ -151,7 +160,7 @@ class Model extends \lithium\core\StaticObject {
*
* @var array
*/
- protected static $_classes = array(
+ protected $_classes = array(
'connections' => 'lithium\data\Connections',
'query' => 'lithium\data\model\Query',
'validator' => 'lithium\util\Validator'
@@ -212,6 +221,13 @@ class Model extends \lithium\core\StaticObject {
);
/**
+ * Array of closures used to lazily initialize metadata.
+ *
+ * @var array
+ */
+ protected $_initializers = array();
+
+ /**
* Stores the data schema.
*
* The schema is lazy-loaded by the first call to `Model::schema()`, unless it has been
@@ -303,7 +319,15 @@ class Model extends \lithium\core\StaticObject {
* @see lithium\data\Model::config()
* @var array
*/
- protected static $_autoConfig = array('meta', 'finders', 'query', 'schema', 'classes');
+ protected static $_autoConfig = array(
+ 'init',
+ 'initializers',
+ 'meta',
+ 'finders',
+ 'query',
+ 'schema',
+ 'classes'
+ );
/**
* Configures the model for use. This method will set the `Model::$_schema`, `Model::$_meta`,
@@ -339,6 +363,18 @@ public static function config(array $config = array()) {
}
/**
+ * Initializer function called just after the model instanciation.
+ *
+ * Example to disable the `_init()` call :
+ * {{{
+ * Posts::config(array('init' => false));
+ * }}}
+
+ * @return void
+ */
+ protected function _init() {}
+
+ /**
* Init default connection options and connects default finders.
*
* This method will set the `Model::$_schema`, `Model::$_meta`, `Model::$_finders` class
@@ -347,7 +383,7 @@ public static function config(array $config = array()) {
* @param string $class The fully-namespaced class name to initialize.
* @return object Returns the initialized model instance.
*/
- protected static function _init($class) {
+ protected static function _initialize($class) {
$self = static::$_instances[$class];
if (isset(static::$_initialized[$class]) && static::$_initialized[$class]) {
@@ -360,7 +396,8 @@ protected static function _init($class) {
$meta = array();
$schema = array();
$source = array();
- $classes = static::$_classes;
+ $classes = $self->_classes;
+ $initializers = array();
foreach (static::_parents() as $parent) {
$parentConfig = get_class_vars($parent);
@@ -383,7 +420,7 @@ protected static function _init($class) {
$conn = $classes['connections']::get($tmp['connection']);
$source = (($conn) ? $conn->configureClass($class) : array()) + $source;
}
- static::$_classes = $classes;
+ $self->_classes = $classes;
$name = static::_name();
$local = compact('class', 'name') + $self->_meta;
@@ -393,15 +430,58 @@ protected static function _init($class) {
if (is_object($schema)) {
$schema = $schema->fields();
}
- $self->schema()->append($schema + $source['schema']);
+
+ $meta = &$self->_meta;
+
+ $self->_initializers += array(
+ 'source' => function(&$meta) {
+ if ($meta['source'] === null) {
+ $meta['source'] = Inflector::tableize($meta['name']);
+ }
+ },
+ 'title' => function(&$meta) use ($class) {
+ if (!$meta['title']) {
+ $titleKeys = array('title', 'name');
+
+ if (isset($meta['key'])) {
+ $titleKeys = array_merge($titleKeys, (array) $meta['key']);
+ }
+ $meta['title'] = $class::hasField($titleKeys);
+ }
+ }
+ );
+
+ $self::schema()->append($schema + $source['schema']);
$self->_finders += $source['finders'] + $self->_findFilters();
static::_relationsToLoad();
+
+ if ($self->_init) {
+ $self->_init();
+ }
return $self;
}
/**
+ * Returns an instance of a class with given `config`. The `name` could be a key from the
+ * `classes` array, a fully-namespaced class name, or an object. Typically this method is used
+ * in `_init` to create the dependencies used in the current class.
+ *
+ * @param string|object $name A `classes` alias or fully-namespaced class name.
+ * @param array $options The configuration passed to the constructor.
+ * @return object
+ */
+ protected static function _instance($name, array $options = array()) {
+ $self = static::_object();
+ if (is_string($name) && isset($self->_classes[$name])) {
+ $name = $self->_classes[$name];
+ }
+ return Libraries::instance(null, $name, $options);
+ }
+
+
+ /**
* Allows the use of syntactic-sugar like `Model::all()` instead of `Model::find('all')`.
*
* @see lithium\data\Model::find()
@@ -566,15 +646,9 @@ public static function meta($key = null, $value = null) {
if (!$self->_meta['initialized']) {
$self->_meta['initialized'] = true;
- if ($self->_meta['source'] === null) {
- $self->_meta['source'] = Inflector::tableize($self->_meta['name']);
+ foreach($self->_initializers as $key => $closure) {
+ $closure($self->_meta);
}
- $titleKeys = array('title', 'name');
-
- if (isset($self->_meta['key'])) {
- $titleKeys = array_merge($titleKeys, (array) $self->_meta['key']);
- }
- $self->_meta['title'] = $self->_meta['title'] ?: static::hasField($titleKeys);
}
if (is_array($key) || !$key || $value) {
return $self->_meta;
@@ -1006,7 +1080,7 @@ public function validates($entity, array $options = array()) {
);
$options += $defaults;
$self = static::_object();
- $validator = static::$_classes['validator'];
+ $validator = $self->_classes['validator'];
$params = compact('entity', 'options');
$filter = function($parent, $params) use (&$self, $validator) {
@@ -1105,7 +1179,7 @@ public static function remove($conditions = array(), array $options = array()) {
*/
public static function &connection() {
$self = static::_object();
- $connections = static::$_classes['connections'];
+ $connections = $self->_classes['connections'];
$name = isset($self->_meta['connection']) ? $self->_meta['connection'] : null;
if ($conn = $connections::get($name)) {
@@ -1170,10 +1244,9 @@ protected static function &_object() {
$class = get_called_class();
if (!isset(static::$_instances[$class])) {
- static::$_instances[$class] = new $class();
static::config();
}
- $object = static::_init($class);
+ $object = static::_initialize($class);
return $object;
}
View
69 tests/cases/data/ModelTest.php
@@ -16,6 +16,7 @@
use lithium\data\entity\Record;
use lithium\tests\mocks\data\MockTag;
use lithium\tests\mocks\data\MockPost;
+use lithium\tests\mocks\data\MockPostFiltered;
use lithium\tests\mocks\data\MockComment;
use lithium\tests\mocks\data\MockTagging;
use lithium\tests\mocks\data\MockCreator;
@@ -64,6 +65,7 @@ public function tearDown() {
MockComment::reset();
MockPostForValidates::reset();
MockCreator::reset();
+ MockPostFiltered::reset();
}
public function testOverrideMeta() {
@@ -795,11 +797,76 @@ public function testLazyLoad() {
$object = MockPost::invokeMethod('_object');
$object->belongsTo = array('Unexisting');
MockPost::config();
- MockPost::invokeMethod('_init', array('lithium\tests\mocks\data\MockPost'));
+ MockPost::invokeMethod('_initialize', array('lithium\tests\mocks\data\MockPost'));
$exception = 'Related model class \'lithium\tests\mocks\data\Unexisting\' not found.';
$this->expectException($exception);
MockPost::relations('Unexisting');
}
+
+ public function testInit() {
+ MockPostFiltered::config();
+ $this->assertFalse(MockPostFiltered::$initCalled);
+ MockPostFiltered::invokeMethod('_object');
+ $this->assertTrue(MockPostFiltered::$initCalled);
+ }
+
+ public function testFilterInInit() {
+ $this->assertEqual('bob filtered static', MockPostFiltered::filteredStatic('bob'));
+ $entity = MockPostFiltered::create();
+ $this->assertEqual('bill filtered dynamic', $entity->filteredDynamic('bill'));
+
+ MockPostFiltered::reset();
+ MockPostFiltered::config(array('init' => false));
+ $this->assertEqual('bob', MockPostFiltered::filteredStatic('bob'));
+ $entity = MockPostFiltered::create();
+ $this->assertEqual('bill', $entity->filteredDynamic('bill'));
+ }
+
+ public function testlazilyMetadataInit() {
+ MockPostFiltered::config(array(
+ 'schema' => new Schema(array(
+ 'fields' => array(
+ 'id' => array('type' => 'integer'),
+ 'name' => array('type' => 'string'),
+ 'label' => array('type' => 'string')
+ )
+ )),
+ ));
+
+ $this->assertEqual('mock_post_filtereds', MockPostFiltered::meta('source'));
+ $this->assertEqual('name', MockPostFiltered::meta('title'));
+
+ MockPostFiltered::reset();
+
+ MockPostFiltered::config(array(
+ 'schema' => new Schema(array(
+ 'fields' => array(
+ 'id' => array('type' => 'integer'),
+ 'name' => array('type' => 'string'),
+ 'label' => array('type' => 'string')
+ )
+ )),
+ 'initializers' => array(
+ 'source' => function(&$meta) {
+ if ($meta['source'] === null) {
+ $meta['source'] = 'post_filtereds';
+ }
+ },
+ 'title' => function(&$meta) {
+ if (!$meta['title']) {
+ $meta['title'] = 'label';
+ }
+ },
+ 'custom' => function(&$meta) {
+ $meta['custom'] = 'customData';
+ }
+ )
+ ));
+
+ $this->assertEqual('post_filtereds', MockPostFiltered::meta('source'));
+ $this->assertEqual('label', MockPostFiltered::meta('title'));
+ $this->assertEqual('customData', MockPostFiltered::meta('custom'));
+ }
}
?>
View
57 tests/mocks/data/MockPostFiltered.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2012, Union of RAD(http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\tests\mocks\data;
+
+use lithium\data\Schema;
+
+class MockPostFiltered extends \lithium\tests\mocks\data\MockBase {
+
+ public $hasMany = array('MockComment');
+
+ public static $connection = null;
+
+ public static $initCalled = false;
+
+ protected $_meta = array('connection' => false, 'key' => 'id');
+
+ public function _init() {
+ parent::_init();
+
+ static::applyFilter('filteredStatic', function($self, $params, $chain) {
+ $response = $chain->next($self, $params, $chain);
+ return $response . ' filtered static';
+ });
+
+ static::applyFilter('filteredDynamic', function($self, $params, $chain) {
+ $response = $chain->next($self, $params, $chain);
+ return $response . ' filtered dynamic';
+ });
+
+ static::$initCalled = true;
+ }
+
+ public function filteredDynamic($entity, $value) {
+ $params = compact('value');
+
+ return static::_filter(__METHOD__, $params, function($self, $params) {
+ return $params['value'];
+ });
+ }
+
+ public static function filteredStatic($value) {
+ $params = compact('value');
+
+ return static::_filter(__FUNCTION__, $params, function($self, $params) {
+ return $params['value'];
+ });
+ }
+
+}
+
+?>
Something went wrong with that request. Please try again.