Browse Source

Merge pull request #6206 from cakephp/faster-type-casting

Optimizing type casting in ResultSet.
Mark Story 11 years ago
parent
commit
59a0521570
2 changed files with 90 additions and 24 deletions
  1. 4 5
      src/Database/Type/IntegerType.php
  2. 86 19
      src/ORM/ResultSet.php

+ 4 - 5
src/Database/Type/IntegerType.php

@@ -28,9 +28,9 @@ class IntegerType extends \Cake\Database\Type
     /**
      * Convert integer data into the database format.
      *
-     * @param string|resource $value The value to convert.
+     * @param mixed $value The value to convert.
      * @param Driver $driver The driver instance to convert with.
-     * @return string|resource
+     * @return int
      */
     public function toDatabase($value, Driver $driver)
     {
@@ -46,10 +46,9 @@ class IntegerType extends \Cake\Database\Type
     /**
      * Convert integer values to PHP integers
      *
-     * @param null|string|resource $value The value to convert.
+     * @param mixed $value The value to convert.
      * @param Driver $driver The driver instance to convert with.
-     * @return resource
-     * @throws \Cake\Core\Exception\Exception
+     * @return int
      */
     public function toPHP($value, Driver $driver)
     {

+ 86 - 19
src/ORM/ResultSet.php

@@ -152,6 +152,15 @@ class ResultSet implements ResultSetInterface
     protected $_types = [];
 
     /**
+     * The Database driver object.
+     *
+     * Cached in a property to avoid multiple calls to the same function.
+     *
+     * @var \Cake\Database\Driver
+     */
+    protected $_driver;
+
+    /**
      * Constructor
      *
      * @param \Cake\ORM\Query $query Query from where results come
@@ -162,6 +171,7 @@ class ResultSet implements ResultSetInterface
         $repository = $query->repository();
         $this->_query = $query;
         $this->_statement = $statement;
+        $this->_driver = $driver = $this->_query->connection()->driver();
         $this->_defaultTable = $this->_query->repository();
         $this->_calculateAssociationMap();
         $this->_hydrate = $this->_query->hydrate();
@@ -169,6 +179,7 @@ class ResultSet implements ResultSetInterface
         $this->_useBuffering = $query->bufferResults();
         $this->_defaultAlias = $this->_defaultTable->alias();
         $this->_calculateColumnMap();
+        $this->_calculateTypeMap();
 
         if ($this->_useBuffering) {
             $count = $this->count();
@@ -381,6 +392,74 @@ class ResultSet implements ResultSetInterface
     }
 
     /**
+     * Creates a map of Type converter classes for each of the columns that should
+     * be fetched by this object.
+     *
+     * @return void
+     */
+    protected function _calculateTypeMap()
+    {
+        if (isset($this->_map[$this->_defaultAlias])) {
+            $this->_types[$this->_defaultAlias] = $this->_getTypes(
+                $this->_defaultTable,
+                $this->_map[$this->_defaultAlias]
+            );
+        }
+
+        foreach ($this->_matchingMapColumns as $alias => $keys) {
+            $this->_types[$alias] = $this->_getTypes(
+                $this->_matchingMap[$alias]['instance']->target(),
+                $keys
+            );
+        }
+
+        foreach ($this->_containMap as $assoc) {
+            $alias = $assoc['alias'];
+            if (isset($this->_types[$alias]) || !$assoc['canBeJoined'] || !isset($this->_map[$alias])) {
+                continue;
+            }
+            $this->_types[$alias] = $this->_getTypes(
+                $assoc['instance']->target(),
+                $this->_map[$alias]
+            );
+        }
+    }
+
+    /**
+     * Returns the Type classes for each of the passed fields belonging to the
+     * table.
+     *
+     * @param \Cake\ORM\Table $table The table from which to get the schema
+     * @param array $fields The fields whitelist to use for fields in the schema.
+     * @return array
+     */
+    protected function _getTypes($table, $fields)
+    {
+        $types = [];
+        $schema = $table->schema();
+        $map = array_keys(Type::map() + ['string' => 1, 'text' => 1, 'boolean' => 1]);
+        $typeMap = array_combine(
+            $map,
+            array_map(['Cake\Database\Type', 'build'], $map)
+        );
+
+        foreach (['string', 'text'] as $t) {
+            if (get_class($typeMap[$t]) === 'Cake\Database\Type') {
+                unset($typeMap[$t]);
+            }
+        }
+
+        foreach (array_intersect($fields, $schema->columns()) as $col) {
+            $typeName = $schema->columnType($col);
+            if (isset($typeMap[$typeName])) {
+                $types[$col] = $typeMap[$typeName];
+            }
+        }
+
+        return $types;
+    }
+
+    /**
      * Helper function to fetch the next result from the statement or
      * seeded results.
      *
@@ -419,7 +498,7 @@ class ResultSet implements ResultSetInterface
         foreach ($this->_matchingMapColumns as $alias => $keys) {
             $matching = $this->_matchingMap[$alias];
             $results['_matchingData'][$alias] = $this->_castValues(
-                $matching['instance']->target(),
+                $alias,
                 array_combine(
                     $keys,
                     array_intersect_key($row, $keys)
@@ -440,7 +519,7 @@ class ResultSet implements ResultSetInterface
 
         if (isset($presentAliases[$defaultAlias])) {
             $results[$defaultAlias] = $this->_castValues(
-                $this->_defaultTable,
+                $defaultAlias,
                 $results[$defaultAlias]
             );
         }
@@ -464,7 +543,7 @@ class ResultSet implements ResultSetInterface
             unset($presentAliases[$alias]);
 
             if ($assoc['canBeJoined']) {
-                $results[$alias] = $this->_castValues($target, $results[$alias]);
+                $results[$alias] = $this->_castValues($assoc['alias'], $results[$alias]);
 
                 $hasData = false;
                 foreach ($results[$alias] as $v) {
@@ -512,26 +591,14 @@ class ResultSet implements ResultSetInterface
      * Casts all values from a row brought from a table to the correct
      * PHP type.
      *
-     * @param Table $table The table object
+     * @param string $alias The table object alias
      * @param array $values The values to cast
      * @return array
      */
-    protected function _castValues($table, $values)
+    protected function _castValues($alias, $values)
     {
-        $alias = $table->alias();
-        $driver = $this->_query->connection()->driver();
-        if (empty($this->_types[$alias])) {
-            $schema = $table->schema();
-            foreach ($schema->columns() as $col) {
-                $this->_types[$alias][$col] = Type::build($schema->columnType($col));
-            }
-        }
-
-        foreach ($values as $field => $value) {
-            if (!isset($this->_types[$alias][$field])) {
-                continue;
-            }
-            $values[$field] = $this->_types[$alias][$field]->toPHP($value, $driver);
+        foreach ($this->_types[$alias] as $field => $type) {
+            $values[$field] = $type->toPHP($values[$field], $this->_driver);
         }
 
         return $values;