diff --git a/include/class.orm.php b/include/class.orm.php index cbb73ee296c201a5be10a329f55e6a63e3f17c31..92d2c6b0fbb51e3fc6fd6e472fb2adaca4411b0f 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -51,6 +51,7 @@ class ModelMeta implements ArrayAccess { var $meta = array(); var $new; var $subclasses = array(); + var $fields; function __construct($model) { $this->model = $model; @@ -68,6 +69,20 @@ class ModelMeta implements ArrayAccess { $meta = $meta + self::$base; } + // Short circuit the meta-data processing if APCu is available. + // This is preferred as the meta-data is unlikely to change unless + // osTicket is upgraded, (then the upgrader calls the + // flushModelCache method to clear this cache). Also, GIT_VERSION is + // used in the APC key which should be changed if new code is + // deployed. + if (function_exists('apcu_store')) { + $loaded = false; + $apc_key = SECRET_SALT.GIT_VERSION."/orm/{$this->model}"; + $this->meta = apcu_fetch($apc_key, $loaded); + if ($loaded) + return; + } + if (!$meta['view']) { if (!$meta['table']) throw new OrmConfigurationException( @@ -93,6 +108,10 @@ class ModelMeta implements ArrayAccess { } unset($j); $this->meta = $meta; + + if (function_exists('apcu_store')) { + apcu_store($apc_key, $this->meta); + } } function extend(ModelMeta $child, $meta) { @@ -188,8 +207,6 @@ class ModelMeta implements ArrayAccess { } function offsetGet($field) { - if (!isset($this->meta[$field])) - $this->setupLazy($field); return $this->meta[$field]; } function offsetSet($field, $what) { @@ -202,31 +219,33 @@ class ModelMeta implements ArrayAccess { throw new Exception('Model MetaData is immutable'); } - function setupLazy($what) { - switch ($what) { - case 'fields': - $this->meta['fields'] = self::inspectFields(); - break; - default: - throw new Exception($what . ': No such meta-data'); - } + /** + * Fetch the column names of the table used to persist instances of this + * model in the database. + */ + function getFieldNames() { + if (!isset($this->fields)) + $this->fields = self::inspectFields(); + return $this->fields; } /** * Create a new instance of the model, optionally hydrating it with the * given hash table. The constructor is not called, which leaves the * default constructor free to assume new object status. + * + * Three methods were considered, with runtime for 10000 iterations + * * unserialze('O:9:"ModelBase":0:{}') - 0.0671s + * * new ReflectionClass("ModelBase")->newInstanceWithoutConstructor() + * - 0.0478s + * * and a hybrid by cloning the reflection class instance - 0.0335s */ function newInstance($props=false) { if (!isset($this->new)) { - $this->new = sprintf( - 'O:%d:"%s":0:{}', - strlen($this->model), $this->model - ); + $rc = new ReflectionClass($this->model); + $this->new = $rc->newInstanceWithoutConstructor(); } - // TODO: Compare timing between unserialize() and - // ReflectionClass::newInstanceWithoutConstructor - $instance = unserialize($this->new); + $instance = clone $this->new; // Hydrate if props were included if (is_array($props)) { $instance->ht = $props; @@ -252,7 +271,7 @@ class ModelMeta implements ArrayAccess { static function flushModelCache() { if (self::$model_cache) - @apc_clear_cache('user'); + @apcu_clear_cache('user'); } } @@ -346,7 +365,7 @@ class VerySimpleModel { return null; // Check to see if the column referenced is actually valid - if (in_array($field, static::getMeta('fields'))) + if (in_array($field, static::getMeta()->getFieldNames())) return null; throw new OrmException(sprintf(__('%s: %s: Field not defined'), @@ -1300,16 +1319,17 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl // Load defaults from model $model = $this->model; + $meta = $model::getMeta(); $query = clone $this; $options += $this->options; if ($options['nosort']) $query->ordering = array(); - elseif (!$query->ordering && $model::getMeta('ordering')) - $query->ordering = $model::getMeta('ordering'); - if (false !== $query->related && !$query->values && $model::getMeta('select_related')) - $query->related = $model::getMeta('select_related'); - if (!$query->defer && $model::getMeta('defer')) - $query->defer = $model::getMeta('defer'); + elseif (!$query->ordering && $meta['ordering']) + $query->ordering = $meta['ordering']; + if (false !== $query->related && !$query->values && $meta['select_related']) + $query->related = $meta['select_related']; + if (!$query->defer && $meta['defer']) + $query->defer = $meta['defer']; $class = $options['compiler'] ?: $this->compiler; $compiler = new $class($options); @@ -1698,7 +1718,7 @@ class InstrumentedList extends ModelInstanceManager { */ function window($constraint) { $model = $this->model; - $fields = $model::getMeta('fields'); + $fields = $model::getMeta()->getFieldNames(); $key = $this->key; foreach ($constraint as $field=>$value) { if (!is_string($field) || false === in_array($field, $fields)) @@ -2370,10 +2390,11 @@ class MySqlCompiler extends SqlCompiler { if (!isset($rmodel)) $rmodel = $model; // Support inline views - $table = ($rmodel::getMeta('view')) + $rmeta = $rmodel::getMeta(); + $table = ($rmeta['view']) // XXX: Support parameters from the nested query ? $rmodel::getSqlAddParams($this) - : $this->quote($rmodel::getMeta('table')); + : $this->quote($rmeta['table']); $base = "{$join}{$table} {$alias}"; return array($base, $constraints); } @@ -2507,14 +2528,15 @@ class MySqlCompiler extends SqlCompiler { // Compile the field listing $fields = $group_by = array(); - $table = $this->quote($model::getMeta('table')).' '.$rootAlias; + $meta = $model::getMeta(); + $table = $this->quote($meta['table']).' '.$rootAlias; // Handle related tables if ($queryset->related) { $count = 0; $fieldMap = $theseFields = array(); $defer = $queryset->defer ?: array(); // Add local fields first - foreach ($model::getMeta('fields') as $f) { + foreach ($meta->getFieldNames() as $f) { // Handle deferreds if (isset($defer[$f])) continue; @@ -2536,7 +2558,7 @@ class MySqlCompiler extends SqlCompiler { $theseFields = array(); list($alias, $fmodel) = $this->getField($full_path, $model, array('table'=>true, 'model'=>true)); - foreach ($fmodel::getMeta('fields') as $f) { + foreach ($fmodel::getMeta()->getFieldNames() as $f) { // Handle deferreds if (isset($defer[$sr . '__' . $f])) continue; @@ -2573,7 +2595,7 @@ class MySqlCompiler extends SqlCompiler { // Simple selection from one table elseif (!$queryset->aggregated) { if ($queryset->defer) { - foreach ($model::getMeta('fields') as $f) { + foreach ($meta->getFieldNames() as $f) { if (isset($queryset->defer[$f])) continue; $fields[$rootAlias .'.'. $this->quote($f)] = true; @@ -2601,7 +2623,7 @@ class MySqlCompiler extends SqlCompiler { } // If no group by has been set yet, use the root model pk if (!$group_by && !$queryset->aggregated && !$queryset->distinct) { - foreach ($model::getMeta('pk') as $pk) + foreach ($meta['pk'] as $pk) $group_by[] = $rootAlias .'.'. $pk; } }