Skip to content
Extraits de code Groupes Projets
DataTable.php 63,9 ko
Newer Older
diosmosis's avatar
diosmosis a validé
     * @return Row|false The last row or `false` if it cannot be found.
     */
    public function getLastRow()
    {
        if (!is_null($this->summaryRow)) {
            return $this->summaryRow;
        }

        if (count($this->rows) == 0) {
            return false;
        }
diosmosis's avatar
diosmosis a validé

        return end($this->rows);
     * Returns the number of rows in the entire DataTable hierarchy. This is the number of rows in this DataTable
     * summed with the row count of each descendant subtable.
     *
     * @return int
     */
    public function getRowsCountRecursive()
    {
        $totalCount = 0;
        foreach ($this->rows as $row) {
            $subTable = $row->getSubtable();
            if ($subTable) {
                $count = $subTable->getRowsCountRecursive();
                $totalCount += $count;
            }
        }

        $totalCount += $this->getRowsCount();
        return $totalCount;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Delete a column by name in every row. This change is NOT applied recursively to all
     * subtables.
diosmosis's avatar
diosmosis a validé
     * @param string $name Column name to delete.
     */
    public function deleteColumn($name)
    {
        $this->deleteColumns(array($name));
    }

    public function __sleep()
    {
        return array('rows', 'summaryRow');
diosmosis's avatar
diosmosis a validé
     * Rename a column in every row. This change is applied recursively to all subtables.
diosmosis's avatar
diosmosis a validé
     * @param string $oldName Old column name.
     * @param string $newName New column name.
    public function renameColumn($oldName, $newName)
        foreach ($this->rows as $row) {
            $row->renameColumn($oldName, $newName);
            $subTable = $row->getSubtable();
            if ($subTable) {
                $subTable->renameColumn($oldName, $newName);
            }
        }
        if (!is_null($this->summaryRow)) {
            $this->summaryRow->renameColumn($oldName, $newName);
        }
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Deletes several columns by name in every row.
diosmosis's avatar
diosmosis a validé
     * @param array $names List of column names to delete.
     * @param bool $deleteRecursiveInSubtables Whether to apply this change to all subtables or not.
     */
    public function deleteColumns($names, $deleteRecursiveInSubtables = false)
    {
        foreach ($this->rows as $row) {
            foreach ($names as $name) {
                $row->deleteColumn($name);
            }
            $subTable = $row->getSubtable();
            if ($subTable) {
                $subTable->deleteColumns($names, $deleteRecursiveInSubtables);
            }
        }
        if (!is_null($this->summaryRow)) {
            foreach ($names as $name) {
                $this->summaryRow->deleteColumn($name);
            }
        }
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Deletes a row by ID.
sgiehl's avatar
sgiehl a validé
     *
diosmosis's avatar
diosmosis a validé
     * @param int $id The row ID.
     * @throws Exception If the row `$id` cannot be found.
     */
    public function deleteRow($id)
    {
        if ($id === self::ID_SUMMARY_ROW) {
            $this->summaryRow = null;
            return;
        }
        if (!isset($this->rows[$id])) {
            throw new Exception("Trying to delete unknown row with idkey = $id");
        }
        unset($this->rows[$id]);
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Deletes rows from `$offset` to `$offset + $limit`.
diosmosis's avatar
diosmosis a validé
     * @param int $offset The offset to start deleting rows from.
     * @param int|null $limit The number of rows to delete. If `null` all rows after the offset
     *                        will be removed.
     * @return int The number of rows deleted.
     */
    public function deleteRowsOffset($offset, $limit = null)
    {
        if ($limit === 0) {
            return 0;
        }

        $count = $this->getRowsCount();
        if ($offset >= $count) {
            return 0;
        }

        // if we delete until the end, we delete the summary row as well
        if (is_null($limit)
            || $limit >= $count
        ) {
            $this->summaryRow = null;
        }

        if (is_null($limit)) {
            array_splice($this->rows, $offset, $limit);
diosmosis's avatar
diosmosis a validé
     * Deletes a set of rows by ID.
diosmosis's avatar
diosmosis a validé
     * @param array $rowIds The list of row IDs to delete.
     * @throws Exception If a row ID cannot be found.
diosmosis's avatar
diosmosis a validé
    public function deleteRows(array $rowIds)
diosmosis's avatar
diosmosis a validé
        foreach ($rowIds as $key) {
diosmosis's avatar
diosmosis a validé
     * Returns a string representation of this DataTable for convenient viewing.
     * _Note: This uses the **html** DataTable renderer._
     *
     * @return string
     */
    public function __toString()
    {
        $renderer->setTable($this);
        return (string)$renderer;
    }

    /**
diosmosis's avatar
diosmosis a validé
     * Returns true if both DataTable instances are exactly the same.
diosmosis's avatar
diosmosis a validé
     * DataTables are equal if they have the same number of rows, if
     * each row has a label that exists in the other table, and if each row
     * is equal to the row in the other table with the same label. The order
     * of rows is not important.
     * @param \Piwik\DataTable $table1
     * @param \Piwik\DataTable $table2
    public static function isEqual(DataTable $table1, DataTable $table2)
    {
        $table1->rebuildIndex();
        $table2->rebuildIndex();

        if ($table1->getRowsCount() != $table2->getRowsCount()) {
            return false;
        }

        $rows1 = $table1->getRows();

        foreach ($rows1 as $row1) {
            $row2 = $table2->getRowFromLabel($row1->getColumn('label'));
            if ($row2 === false
diosmosis's avatar
diosmosis a validé
     * Serializes an entire DataTable hierarchy and returns the array of serialized DataTables.
diosmosis's avatar
diosmosis a validé
     * The first element in the returned array will be the serialized representation of this DataTable.
     * Every subsequent element will be a serialized subtable.
diosmosis's avatar
diosmosis a validé
     * This DataTable and subtables can optionally be truncated before being serialized. In most
     * cases where DataTables can become quite large, they should be truncated before being persisted
     * in an archive.
     * The result of this method is intended for use with the {@link ArchiveProcessor::insertBlobRecord()} method.
diosmosis's avatar
diosmosis a validé
     * @throws Exception If infinite recursion detected. This will occur if a table's subtable is one of its parent tables.
     * @param int $maximumRowsInDataTable If not null, defines the maximum number of rows allowed in the serialized DataTable.
     * @param int $maximumRowsInSubDataTable If not null, defines the maximum number of rows allowed in serialized subtables.
     * @param string $columnToSortByBeforeTruncation The column to sort by before truncating, eg, `Metrics::INDEX_NB_VISITS`.
     * @return array The array of serialized DataTables:
diosmosis's avatar
diosmosis a validé
     *                   array(
     *                       // this DataTable (the root)
     *                       0 => 'eghuighahgaueytae78yaet7yaetae',
diosmosis's avatar
diosmosis a validé
     *                       // a subtable
     *                       1 => 'gaegae gh gwrh guiwh uigwhuige',
diosmosis's avatar
diosmosis a validé
     *                       // another subtable
     *                       2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE',
diosmosis's avatar
diosmosis a validé
     *                       // etc.
     *                   );
     */
    public function getSerialized($maximumRowsInDataTable = null,
                                  $maximumRowsInSubDataTable = null,
                                  $columnToSortByBeforeTruncation = null)
    {
        static $depth = 0;
        // make sure subtableIds are consecutive from 1 to N
        static $subtableId = 0;

        if ($depth > self::$maximumDepthLevelAllowed) {
            $depth = 0;
            throw new Exception("Maximum recursion level of " . self::$maximumDepthLevelAllowed . " reached. Maybe you have set a DataTable\Row with an associated DataTable belonging already to one of its parent tables?");
        }
        if (!is_null($maximumRowsInDataTable)) {
                array($maximumRowsInDataTable - 1,
                      $columnToSortByBeforeTruncation,
                      $filterRecursive = false)
        $consecutiveSubtableIds = array();
        $forcedId = $subtableId;

        // For each row, get the serialized row
        // If it is associated to a sub table, get the serialized table recursively ;
        // but returns all serialized tables and subtable in an array of 1 dimension
        $aSerializedDataTable = array();
            $subTable = $row->getSubtable();
            if ($subTable) {
                $consecutiveSubtableIds[$id] = ++$subtableId;
                $depth++;
                $aSerializedDataTable = $aSerializedDataTable + $subTable->getSerialized($maximumRowsInSubDataTable, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation);
                $depth--;
            }
        }

        // if the datatable is the parent we force the Id at 0 (this is part of the specification)
        if ($depth == 0) {
            $forcedId = 0;
        }

        // we then serialize the rows and store them in the serialized dataTable
        $rows = array();
        foreach ($this->rows as $id => $row) {
            if (array_key_exists($id, $consecutiveSubtableIds)) {
                $backup = $row->subtableId;
                $row->subtableId = $consecutiveSubtableIds[$id];
                $rows[$id] = $row->export();
                $row->subtableId = $backup;
            } else {
                $rows[$id] = $row->export();
            }
        }
        if (isset($this->summaryRow)) {
            $rows[self::ID_SUMMARY_ROW] = $this->summaryRow->export();
        $aSerializedDataTable[$forcedId] = serialize($rows);
        unset($rows);

    private static $previousRowClasses = array('O:39:"Piwik\DataTable\Row\DataTableSummaryRow"', 'O:19:"Piwik\DataTable\Row"', 'O:36:"Piwik_DataTable_Row_DataTableSummary"', 'O:19:"Piwik_DataTable_Row"');
    private static $rowClassToUseForUnserialize = 'O:29:"Piwik_DataTable_SerializedRow"';

    /**
     * It is faster to unserialize existing serialized Row instances to "Piwik_DataTable_SerializedRow" and access the
     * `$row->c` property than implementing a "__wakeup" method in the Row instance to map the "$row->c" to $row->columns
     * etc. We're talking here about 15% faster reports aggregation in some cases. To be concrete: We have a test where
     * Archiving a year takes 1700 seconds with "__wakeup" and 1400 seconds with this method. Yes, it takes 300 seconds
     * to wake up millions of rows. We should be able to remove this code here end 2015 and use the "__wakeup" way by then.
     * Why? By then most new archives will have only arrays serialized anyway and therefore this mapping is rather an overhead.
     *
     * @param string $serialized
     * @return array
     * @throws Exception In case the unserialize fails
     */
    private function unserializeRows($serialized)
    {
        $serialized = str_replace(self::$previousRowClasses, self::$rowClassToUseForUnserialize, $serialized);
        $rows = unserialize($serialized);

        if ($rows === false) {
            throw new Exception("The unserialization has failed!");
        }

        return $rows;
    }

diosmosis's avatar
diosmosis a validé
     * Adds a set of rows from a serialized DataTable string.
     * See {@link serialize()}.
     * _Note: This function will successfully load DataTables serialized by Piwik 1.X._
     * @param string $serialized A string with the format of a string in the array returned by
     *                                 {@link serialize()}.
     * @throws Exception if `$serialized` is invalid.
    public function addRowsFromSerializedArray($serialized)
        $rows = $this->unserializeRows($serialized);
        if (array_key_exists(self::ID_SUMMARY_ROW, $rows)) {
            if (is_array($rows[self::ID_SUMMARY_ROW])) {
                $this->summaryRow = new Row($rows[self::ID_SUMMARY_ROW]);
            } elseif (isset($rows[self::ID_SUMMARY_ROW]->c)) {
Thomas Steur's avatar
Thomas Steur a validé
                $this->summaryRow = new Row($rows[self::ID_SUMMARY_ROW]->c); // Pre Piwik 2.13
            }
            unset($rows[self::ID_SUMMARY_ROW]);
        foreach ($rows as $id => $row) {
            if (isset($row->c)) {
Thomas Steur's avatar
Thomas Steur a validé
                $this->addRow(new Row($row->c)); // Pre Piwik 2.13
     * Adds multiple rows from an array.
     * You can add row metadata with this method.
     * @param array $array Array with the following structure
diosmosis's avatar
diosmosis a validé
     *                         array(
diosmosis's avatar
diosmosis a validé
     *                                 Row::COLUMNS => array( col1_name => value1, col2_name => value2, ...),
     *                                 Row::METADATA => array( metadata1_name => value1,  ...), // see Row
diosmosis's avatar
diosmosis a validé
     *                         )
     */
    public function addRowsFromArray($array)
    {
        foreach ($array as $id => $row) {
            if (is_array($row)) {
            if ($id == self::ID_SUMMARY_ROW) {
                $this->summaryRow = $row;
            } else {
                $this->addRow($row);
            }
        }
    }

    /**
     * Adds multiple rows from an array containing arrays of column values.
diosmosis's avatar
diosmosis a validé
     * Row metadata cannot be added with this method.
     * @param array $array Array with the following structure:
     *                       array(
     *                             array( col1_name => valueA, col2_name => valueC, ...),
     *                             array( col1_name => valueB, col2_name => valueD, ...),
     *                       )
     * @throws Exception if `$array` is in an incorrect format.
     */
    public function addRowsFromSimpleArray($array)
    {
        if (count($array) === 0) {
            return;
        }

        $exceptionText = " Data structure returned is not convertible in the requested format." .
            " Try to call this method with the parameters '&format=original&serialize=1'" .
            "; you will get the original php data structure serialized." .
            " The data structure looks like this: \n \$data = %s; ";

        // first pass to see if the array has the structure
        // array(col1_name => val1, col2_name => val2, etc.)
        // with val* that are never arrays (only strings/numbers/bool/etc.)
        // if we detect such a "simple" data structure we convert it to a row with the correct columns' names
        $thisIsNotThatSimple = false;

        foreach ($array as $columnValue) {
            if (is_array($columnValue) || is_object($columnValue)) {
                $thisIsNotThatSimple = true;
                break;
            }
        }
        if ($thisIsNotThatSimple === false) {
            // case when the array is indexed by the default numeric index
            if (array_keys($array) == array_keys(array_fill(0, count($array), true))) {
                foreach ($array as $row) {
                    $this->addRow(new Row(array(Row::COLUMNS => array($row))));
                $this->addRow(new Row(array(Row::COLUMNS => $array)));
            }
            // we have converted our simple array to one single row
            // => we exit the method as the job is now finished
            return;
        }

        foreach ($array as $key => $row) {
            // stuff that looks like a line
            if (is_array($row)) {
                /**
                 * We make sure we can convert this PHP array without losing information.
                 * We are able to convert only simple php array (no strings keys, no sub arrays, etc.)
                 *
                 */

                // if the key is a string it means that some information was contained in this key.
                // it cannot be lost during the conversion. Because we are not able to handle properly
                // this key, we throw an explicit exception.
                if (is_string($key)) {
                    // we define an exception we may throw if at one point we notice that we cannot handle the data structure
                    throw new Exception(sprintf($exceptionText, var_export($array, true)));
                }
                // if any of the sub elements of row is an array we cannot handle this data structure...
                foreach ($row as $subRow) {
                    if (is_array($subRow)) {
                        throw new Exception(sprintf($exceptionText, var_export($array, true)));
                $row = new Row(array(Row::COLUMNS => $row));
            } // other (string, numbers...) => we build a line from this value
            else {
                $row = new Row(array(Row::COLUMNS => array($key => $row)));
     * Rewrites the input `$array`
     *     array (
     *         LABEL => array(col1 => X, col2 => Y),
     *         LABEL2 => array(col1 => X, col2 => Y),
     *     )
     * to a DataTable with rows that look like:
     *     array (
     *         array( Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y)),
     *         array( Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)),
     *     )
     * Will also convert arrays like:
     *
     *     array (
     *         LABEL => X,
     *         LABEL2 => Y,
     *     )
     *     array (
     *         array( Row::COLUMNS => array('label' => LABEL, 'value' => X)),
     *         array( Row::COLUMNS => array('label' => LABEL2, 'value' => Y)),
     *     )
     * @param array $array Indexed array, two formats supported, see above.
     * @param array|null $subtablePerLabel An array mapping label values with DataTable instances to associate as a subtable.
    public static function makeFromIndexedArray($array, $subtablePerLabel = null)
        foreach ($array as $label => $row) {
            // Support the case of an $array of single values
            if (!is_array($row)) {
                $row = array('value' => $row);
            }
            // Put the 'label' column first
            $cleanRow[Row::COLUMNS] = array('label' => $label) + $row;
            // Assign subtable if specified
            if (isset($subtablePerLabel[$label])) {
                $cleanRow[Row::DATATABLE_ASSOCIATED] = $subtablePerLabel[$label];
            $table->addRow(new Row($cleanRow));
diosmosis's avatar
diosmosis a validé
     * Sets the maximum depth level to at least a certain value. If the current value is
     * greater than `$atLeastLevel`, the maximum nesting level is not changed.
diosmosis's avatar
diosmosis a validé
     * The maximum depth level determines the maximum number of subtable levels in the
     * DataTable tree. For example, if it is set to `2`, this DataTable is allowed to
     * have subtables, but the subtables are not.
    public static function setMaximumDepthLevelAllowedAtLeast($atLeastLevel)
    {
        self::$maximumDepthLevelAllowed = max($atLeastLevel, self::$maximumDepthLevelAllowed);
        if (self::$maximumDepthLevelAllowed < 1) {
            self::$maximumDepthLevelAllowed = 1;
        }
    }

    /**
     * Returns metadata by name.
     *
     * @param string $name The metadata name.
     * @return mixed|false The metadata value or `false` if it cannot be found.
     */
    public function getMetadata($name)
    {
        if (!isset($this->metadata[$name])) {
            return false;
        }
        return $this->metadata[$name];
    }

    /**
     * Sets a metadata value by name.
     *
     * @param string $name The metadata name.
     * @param mixed $value
     */
    public function setMetadata($name, $value)
    {
        $this->metadata[$name] = $value;
    }

    /**
     * Returns all table metadata.
     *
     * @return array
     */
    public function getAllTableMetadata()
    {
        return $this->metadata;
    }

    /**
     * Sets several metadata values by name.
     * @param array $values Array mapping metadata names with metadata values.
     */
    public function setMetadataValues($values)
    {
        foreach ($values as $name => $value) {
            $this->metadata[$name] = $value;
        }
    }

    /**
     * Sets metadata, erasing existing values.
     * @param array $values Array mapping metadata names with metadata values.
     */
    public function setAllTableMetadata($metadata)
    {
        $this->metadata = $metadata;
    }

    /**
     * Sets the maximum number of rows allowed in this datatable (including the summary
     * row). If adding more then the allowed number of rows is attempted, the extra
diosmosis's avatar
diosmosis a validé
     * rows are summed to the summary row.
diosmosis's avatar
diosmosis a validé
     * @param int $maximumAllowedRows If `0`, the maximum number of rows is unset.
     */
    public function setMaximumAllowedRows($maximumAllowedRows)
    {
        $this->maximumAllowedRows = $maximumAllowedRows;
    }

    /**
     * Traverses a DataTable tree using an array of labels and returns the row
     * it finds or `false` if it cannot find one. The number of path segments that
     * were successfully walked is also returned.
     * If `$missingRowColumns` is supplied, the specified path is created. When
     * a subtable is encountered w/o the required label, a new row is created
     * with the label, and a new subtable is added to the row.
diosmosis's avatar
diosmosis a validé
     * Read [http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods](http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods)
     * for more information about tree walking.
diosmosis's avatar
diosmosis a validé
     * @param array $path The path to walk. An array of label values. The first element
     *                    refers to a row in this DataTable, the second in a subtable of
     *                    the first row, the third a subtable of the second row, etc.
     * @param array|bool $missingRowColumns The default columns to use when creating new rows.
sgiehl's avatar
sgiehl a validé
     *                                      If this parameter is supplied, new rows will be
diosmosis's avatar
diosmosis a validé
     *                                      created for path labels that cannot be found.
     * @param int $maxSubtableRows The maximum number of allowed rows in new subtables. New
     *                             subtables are only created if `$missingRowColumns` is provided.
     * @return array First element is the found row or `false`. Second element is
     *               the number of path segments walked. If a row is found, this
     *               will be == to `count($path)`. Otherwise, it will be the index
     *               of the path segment that we could not find.
     */
    public function walkPath($path, $missingRowColumns = false, $maxSubtableRows = 0)
    {
        $pathLength = count($path);

        $table = $this;
        $next = false;
        for ($i = 0; $i < $pathLength; ++$i) {
            $segment = $path[$i];

            $next = $table->getRowFromLabel($segment);
            if ($next === false) {
                // if there is no table to advance to, and we're not adding missing rows, return false
                if ($missingRowColumns === false) {
                    return array(false, $i);
                } else {
                    // if we're adding missing rows, add a new row

                    $row = new DataTableSummaryRow();
                    $row->setColumns(array('label' => $segment) + $missingRowColumns);

                    $next = $table->addRow($row);

                    if ($next !== $row) {
                        // if the row wasn't added, the table is full

                        // Summary row, has no metadata
                        $next->deleteMetadata();
                        return array($next, $i);
                    }
                }
            }

            $table = $next->getSubtable();
            if ($table === false) {
                // if the row has no table (and thus no child rows), and we're not adding
                // missing rows, return false
                if ($missingRowColumns === false) {
                    return array(false, $i);
                } elseif ($i != $pathLength - 1) {
                    // create subtable if missing, but only if not on the last segment

                    $table->setMaximumAllowedRows($maxSubtableRows);
diosmosis's avatar
diosmosis a validé
                    $table->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME]
diosmosis's avatar
diosmosis a validé
                        = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME);
                    $next->setSubtable($table);
                    // Summary row, has no metadata
                    $next->deleteMetadata();
                }
            }
        }

        return array($next, $i);
    }

    /**
     * Returns a new DataTable in which the rows of this table are replaced with the aggregatated rows of all its subtables.
diosmosis's avatar
diosmosis a validé
     * @param string|bool $labelColumn If supplied the label of the parent row will be added to
     *                                 a new column in each subtable row.
     *                                 If set to, `'label'` each subtable row's label will be prepended
diosmosis's avatar
diosmosis a validé
     *                                 w/ the parent row's label. So `'child_label'` becomes
     *                                 `'parent_label - child_label'`.
     * @param bool $useMetadataColumn If true and if `$labelColumn` is supplied, the parent row's
diosmosis's avatar
diosmosis a validé
     *                                label will be added as metadata and not a new column.
     */
    public function mergeSubtables($labelColumn = false, $useMetadataColumn = false)
    {
Thomas Steur's avatar
Thomas Steur a validé
        $result->setAllTableMetadata($this->getAllTableMetadata());
        foreach ($this->getRowsWithoutSummaryRow() as $row) {
            $subtable = $row->getSubtable();
            if ($subtable !== false) {
                $parentLabel = $row->getColumn('label');

                // add a copy of each subtable row to the new datatable
                foreach ($subtable->getRows() as $id => $subRow) {
                    $copy = clone $subRow;

                    // if the summary row, add it to the existing summary row (or add a new one)
                    if ($id == self::ID_SUMMARY_ROW) {
                        $existing = $result->getRowFromId(self::ID_SUMMARY_ROW);
                        if ($existing === false) {
                            $result->addSummaryRow($copy);
                        } else {
diosmosis's avatar
diosmosis a validé
                            $existing->sumRow($copy, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
                        }
                    } else {
                        if ($labelColumn !== false) {
                            // if we're modifying the subtable's rows' label column, then we make
                            // sure to prepend the existing label w/ the parent row's label. otherwise
                            // we're just adding the parent row's label as a new column/metadata.
                            $newLabel = $parentLabel;
                            if ($labelColumn == 'label') {
                                $newLabel .= ' - ' . $copy->getColumn('label');
                            }

                            // modify the child row's label or add new column/metadata
                            if ($useMetadataColumn) {
                                $copy->setMetadata($labelColumn, $newLabel);
                            } else {
                                $copy->setColumn($labelColumn, $newLabel);
                            }
                        }

                        $result->addRow($copy);
                    }
                }
            }
        }
        return $result;
    }

    /**
     * Returns a new DataTable created with data from a 'simple' array.
     * See {@link addRowsFromSimpleArray()}.
     */
    public static function makeFromSimpleArray($array)
    {
        $dataTable->addRowsFromSimpleArray($array);
        return $dataTable;
    }
diosmosis's avatar
diosmosis a validé
     * Creates a new DataTable instance from a serialized DataTable string.
     * See {@link getSerialized()} and {@link addRowsFromSerializedArray()}
diosmosis's avatar
diosmosis a validé
     * for more information on DataTable serialization.
mattab's avatar
mattab a validé
     *
     */
    public static function fromSerializedArray($data)
    {
        $result->addRowsFromSerializedArray($data);
        return $result;
    }

    /**
     * Aggregates the $row columns to this table.
     *
     * $row must have a column "label". The $row will be summed to this table's row with the same label.
     *
     * @param $row
     * @params null|array $columnAggregationOps
    protected function aggregateRowWithLabel(Row $row, $columnAggregationOps)
    {
        $labelToLookFor = $row->getColumn('label');
        if ($labelToLookFor === false) {
            throw new Exception("Label column not found in the table to add in addDataTable()");
        }
        $rowFound = $this->getRowFromLabel($labelToLookFor);
        if ($rowFound === false) {
            if ($labelToLookFor === self::LABEL_SUMMARY_ROW) {
                $this->addSummaryRow($row);
            } else {
                $this->addRow($row);
            }
        } else {
            $rowFound->sumRow($row, $copyMeta = true, $columnAggregationOps);
            // if the row to add has a subtable whereas the current row doesn't
            // we simply add it (cloning the subtable)
            // if the row has the subtable already
            // then we have to recursively sum the subtables
            $subTable = $row->getSubtable();
            if ($subTable) {
                $subTable->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] = $columnAggregationOps;
                $rowFound->sumSubtable($subTable);
            }
        }
    }

    /**
     * @param $row
     */
    protected function aggregateRowFromSimpleTable($row)
    {
        if ($row === false) {
            return;
        }
        $thisRow = $this->getFirstRow();
        if ($thisRow === false) {
            $thisRow = new Row;
            $this->addRow($thisRow);
        }
        $thisRow->sumRow($row, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME));
    }
    /**
     * Unsets all queued filters.
     */
    public function clearQueuedFilters()
    {
        $this->queuedFilters = array();
    }

    /**
     * @return \ArrayIterator|Row[]
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->getRows());

    public function offsetExists($offset)
    {
        $row = $this->getRowFromId($offset);

        return false !== $row;
    }

    public function offsetGet($offset)
    {
        return $this->getRowFromId($offset);
    }

    public function offsetSet($offset, $value)
    {
        $this->rows[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        $this->deleteRow($offset);
    }
Thomas Steur's avatar
Thomas Steur a validé
}