Skip to content

Instantly share code, notes, and snippets.

@asika32764
Forked from eddieajau/DataMapper.php
Created December 30, 2013 02:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save asika32764/8176988 to your computer and use it in GitHub Desktop.
Save asika32764/8176988 to your computer and use it in GitHub Desktop.

The Data Package

...snip...

Data\Mapper

The Data\Mapper class establishes a bridge between data objects and a data source. The purpose is to provide very light coupling from the data object to the mapper. In other words, the data object should not care too much about what mapper is used or where the data is coming from. However, there is usually very tight coupling from the data mapper to the data object. The mapper obviously needs to know a lot about the data source and, to a degree, also the type of data that it is loading.

Note that the mapper class is not intended to be a full Object Relationship Mapper (ORM) but it could be used to interface with established, third-party solutions that provide such features (Doctrine for example).

Public methods

There are six public entry points to the mapper API.

The constructor takes no arguments so the developer is free to add additional arguments (probably relating to the type of data source).

The create method is used to create new data. It expects a Data\Dumpable object. This is an object that defines a dump method and includes Data\Data and Data\Set. If a singular object, like Data\Data is passed to the mapper, and instance of a singular object is expected to be returned. However, if an instance of a Data\Set object is passed to the method, an instance of a Data\Set will be returned.

The delete method is used to remove data from the data source. It expects either a single object identifier (for example, the value of the primary key in a database table), or an array of object identifiers. Nothing is returned.

The find method is used to search for and load data from the data source. It takes an optional where and sort criteria, a paging offset and an paging limit. It returns a Data\Set of objects matching the criteria. If no criteria is supplied, it will return all results subject to the pagination values that are specified. The findOne method works the same as find but it only returns one (the first) result retrieved by find.

The update method is used to update data in the data source. It is otherwise identical to the create method.

Extending the mapper

When extending the mapper, there are four abstract methods to implement and one protected method that can be optionally overriden.

The protected initialise method is called in the Data\Mapper constructor. It can be overriden to support any setup required by the developer.

The abstract doCreate method must be implemented. It takes an array of dumped objects. The objects must be added to the data source, and should add any additional properties that are required (for example, time stamps). The method must return a Data\Set containing the objects that were created in the data source, including additional data that may have been added by the data source (for example, setting the primary key or created time stamps).

The abstract doDelete method must be implemented. It takes an array of unique object identifiers (such as primary keys in a database, cache identifiers, etc). The method must delete the corresponding objects in the data source. Any return value is ignored.

The abstract doFind method must be implemented. It takes the same arguments as the find method. The method must return a Data\Set object regardless of whether any data was found to match the search criteria. If this method accidentally returns more data records than defined by $limit, the calling find method will truncate the data set to the pagination limit that was specificed.

The abstract doUpdate method must be implemented. Like doCreate, it takes an array of dumped objects that must be updated in the data source. The method must return a Data\Set containing the objects that were updated in the data source, including additional data that may have been added by the data source (for example, modified time stamps).

The following basic example shows how a base mapper class could be devised to support database table operations (most DocBlocks are removed for brevity).

namespace Joomla\Database;

use Joomla\Data;

class TableMapper extends Data\Mapper
{
	/**
	 * @var  \Joomla\Database\Driver
	 */
	protected $db;

	/**
	 * @var  string
	 */
	protected $table;

	/**
	 * @var  string
	 */
	protected $tableKey;

	/**
	 * @var  array
	 */
	private $columns;

	public function __construct(Driver $db, $table, $tableKey)
	{
		// As for JTable, set a database driver, the table name and the primary key.
		$this->db = $db;
		$this->table = $table;
		$this->tableKey = $tableKey;

		parent::__construct();
	}

	protected function doCreate(array $input)
	{
		$result = new Data\Set;

		foreach ($input as $object)
		{
			// Ensure only the columns for this table are inserted.
			$row = (object) array_intersect_key((array) $object, $this->columns);
			$this->db->insertObject($this->table, $row, $this->tableKey);
			$result[$row->{$this->tableKey}] = new Data\Data($row);
		}

		return $result;
	}

	protected function doDelete(array $input)
	{
		// Sanitise.
		$input = array_map('intval', $input);

		if (empty($input))
		{
			return;
		}

		$q = $this->db->getQuery(true);
		$q->delete($q->qn($this->table))
			->where($q->qn($this->tableKey) . ' IN (' . implode(',', $input). ')');
		$this->db->setQuery($q)->execute();
	}

	protected function doFind($where = null, $sort = null, $offset = 0, $limit = 0)
	{
		$q = $this->db->getQuery(true);
		$q->select('*')
			->from($q->qn($this->table));

		// A simple example of column-value conditions.
		if (is_array($where) && !empty($where))
		{
			foreach ($where as $column => $value)
			{
				$q->where($q->qn($column) . '=' . $q->q($value));
			}
		}

		// A simple example of column-direction pairs.
		if (is_array($sort) && !empty($sort))
		{
			foreach ($sort as $column => $direction)
			{
				$q->where($q->qn($column) . ' ' . (strtoupper($direction == 'DESC') ? 'DESC' : 'ASC'));
			}
		}

		return new Data\Set($this->db->setQuery($q)->loadObjectList($this->tableKey, 'JData'));
	}

	protected function doUpdate(array $input)
	{
		$result = new Data\Set;

		foreach ($input as $object)
		{
			// Ensure only the columns for this table are updated.
			$row = (object) array_intersect_key((array) $object, $this->columns);
			$this->db->updateObject($this->table, $row, $this->tableKey);
			$result[$row->{$this->tableKey}] = new Data\Data($row);
		}

		return $result;
	}

	protected function initialise()
	{
		// Stash the columns for this table.
		$this->columns = $this->db->getTableColumns($this->table);
	}
}

Mocking the Data Pacakge

The core Data\Object and Data\Set classes are simple value objects and we don't recommend you mock them (the effort of mocking more or less produces the original classes anyway).

To mock a Data\Mapper object you can use the Data\Tests\Mocker class.

use Joomla\Data\Tests\Mocker as DataMocker;

class MyTest extends \PHPUnit_Framework_TestCase
{
    private $instance;

    protected function setUp()
    {
        parent::setUp();

        // Create the mock input object.
        $dataMocker = new DataMocker($this);
        $mockMapper = $dataMocker->createMapper();

        // Set up some test data for `find` and `findOne` to return.
        $dataMocker->set =  new Set(
			array(new Object(array('foo' => 'bar')))
		);

        // Create the test instance injecting the mock dependency.
        $this->instance = new MyClass($mockMapper);
    }
}

The createMapper method will return a mock with the following methods mocked to roughly simulate real behaviour albeit with reduced functionality:

  • find([$where, $sort, $offset, $limit])
  • findOne([$where, $sort])

The Data\Tests\Mocker class supports a public set property (shown in the example) for you to inject a Data\Set object for the mock find and findOne methods to use.

Or, you can provide customised implementations these methods by creating the following methods in your test class respectively:

  • mockMapperFind
  • mockMapperFindOne
<?php
/**
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Database;
use Joomla\Data;
use Joomla\Data\DataMapper;
/**
* Database mapper class.
*
* This class is used to provide a layer between data objects and the database.
*
* Overview:
* - any number of tables can be mapped, providing the relationship is 1:1
* - the primary key must be the same in each table
* - transactional tables are assumed (INNODB)
* - the first table specified is considered to be the primary table from which all other relationship flow
* - only the first table will be deleted; all other tables should cascade using foreign contraints
*
* Example:
*
* $mapper = \Joomla\Database\Mapper(JFactory::getDbo());
* $mapper->setPrimaryKey('content_id)
* ->addTable('jos_content', 'a')
* ->addTable('jos_content_hits', 'h');
*
* $list = $mapper->find(array('content_id', 1));
*
* @since 1.0
*/
class DatabaseMapper extends DataMapper
{
/**
* The database driver that the mapper uses.
*
* @var Driver
* @since 1.0
*/
protected $db = null;
/**
* An associative array of SELECT clauses for each table.
*
* @var array
* @since 1.0
*/
private $selects = array();
/**
* An associative array of two-element arrays 'table alias' => array('column 1', 'column 2')
* of the join fields for the table, if different from the primary key.
*
* @var array
* @since 1.0
*/
private $joins = array();
/**
* The name of the database table column that specifies the primary key for all mapping tables.
* Each table must have the same primary key column.
*
* @var string
* @since 1.0
*/
private $primaryKey = null;
/**
* An associative array of 'table alias' => 'table name' values used by the mapper.
*
* @var array
* @since 1.0
*/
private $tables = array();
/**
* A static cache for the table column specifications. This is common to all mappers to ensure data is loaded only once.
* The data is an associative array of 'column name' => 'column type' values.
*
* @var array
* @since 1.0
*/
private static $columns = array();
/**
* The class constructor.
*
* The primary key are optional. This allows concrete Database\Mapper classes to be instantiated and injected with
* the appropriate mapping associations.
*
* @param Driver $db The database driver.
* @param string $primaryKey The name of the primary key.
* @param array $tables An associative array of mapping table names in the form tableName => $tableAlias.
*
* @since 1.0
*/
public function __construct(DatabaseDriver $db, $primaryKey = null, array $tables = array())
{
parent::__construct();
$this->db = $db;
$this->setPrimaryKey($primaryKey);
foreach ($tables as $tableName => $tableAlias)
{
$this->addTable($tableName, $tableAlias);
}
}
/**
* Adds a table to the mapping list.
*
* The table must include a column that matches the primary key set in the mapper.
*
* @param string $tableName The name of the name.
* @param string $tableAlias The alias to be used for the table.
* @param array $join A two-element array of the join fields between this and another table.
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
public function addTable($tableName, $tableAlias, array $join = null)
{
if (!empty($tableName))
{
$this->tables[$tableAlias ?: $tableName] = $tableName;
if ($join)
{
$this->joins[$tableAlias ?: $tableName] = $join;
}
}
return $this;
}
/**
* Get the primary key for the mapping table.
*
* Note that where a single table is defined, the primary key is not necessarily required.
*
* @return string The name of the primary key column in the database table.
*
* @since 1.0
* @throws \LogicException if the primary key has not been set.
*/
public function getPrimaryKey()
{
if (count($this->tables) > 1 && $this->primaryKey === null)
{
throw new \LogicException(sprintf('Primary key not set in %s', __METHOD__));
}
return $this->primaryKey;
}
/**
* Gets a list of the tables that have been defined for this mapper.
*
* @return array An associative array of 'table alias' => 'table name' values.
*
* @since 1.0
*/
public function getTables()
{
return $this->tables;
}
/**
* Set the primary key for the mapping tables.
*
* The primary key must be available in all the mapping tables (1:1 mapping).
*
* @param string $key The name of the primary key for all the mapping tables.
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
public function setPrimaryKey($key)
{
$this->primaryKey = $key;
return $this;
}
/**
* Sets a custom select clause for a table.
*
* If the table has not been defined, the method call is ignored.
*
* @param string $name The table name or alias (must match what was used in `addTable`).
* @param array|boolean $select An array of column names to add to the SELECT clause, or `true` to include all fields.
* Default: `true`.
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
public function setTableSelect($name, $select)
{
$tables = array_keys($this->getTables());
if (in_array($name, $tables))
{
$this->selects[$name] = $select;
}
return $this;
}
/**
* Adds the base query specification for the doFind method.
*
* @param Query $q The database query.
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
protected function addFindQueryBase(DatabaseQuery $q)
{
$tables = $this->getTables();
$tablesAlias = array_keys($tables);
$primaryTableAlias = reset($tablesAlias);
$primaryTable = array_shift($tables);
$primaryKey = $this->getPrimaryKey();
$q->select($this->getTableSelect($primaryTableAlias, $q->qn($primaryTableAlias) . '.*'));
$q->from($q->qn($primaryTable, $primaryTableAlias));
foreach ($tables as $tableAlias => $tableName)
{
$q->select($this->getTableSelect($tableAlias, $q->qn($tableAlias) . '.*'));
$q->leftJoin(
sprintf(
'%s AS %s ON %s = %s',
$q->qn($tableName),
$q->qn($tableAlias),
isset($this->joins[$tableAlias]) ? $q->qn($this->joins[$tableAlias][0]) : $q->qn("$tableAlias.$primaryKey"),
isset($this->joins[$tableAlias]) ? $q->qn($this->joins[$tableAlias][1]) : $q->qn("$primaryTableAlias.$primaryKey")
)
);
}
return $this;
}
/**
* Adds the sort conditions to the query for doFind.
*
* @param Query $q The database query.
* @param array $sort An associative array defining the sort direction in the form:
* array(column => direction).
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
protected function addFindQuerySort(DatabaseQuery $q, array $sort)
{
foreach ($sort as $column => $direction)
{
$q->order($q->qn($column) . ' ' . (strtoupper($direction) == 'DESC' ? 'DESC' : 'ASC'));
}
return $this;
}
/**
* Adds the where conditions to the query for doFind.
*
* @param Query $q The database query.
* @param mixed $where The criteria by which to search the data source in the form:
* array(column => value)
* If value is an array, the condition is evaluated an an "IN".
*
* @return Mapper Returns itself to allow chaining.
*
* @since 1.0
*/
protected function addFindQueryWhere(DatabaseQuery $q, array $where)
{
foreach ($where as $column => $value)
{
if ($value instanceof ConditionInterface)
{
$value->setDatabase($this->db);
$q->where($q->qn($column) . ' ' . (string) $value);
}
elseif (is_array($value))
{
if (count($value) == 0)
{
throw new \LogicException(sprintf('%s: Empty array criteria for column `%s`', __METHOD__, $column));
}
$q->where($q->qn($column) . ' IN (' . implode(',', $this->db->quote($value)) . ')');
}
else
{
$q->where($q->qn($column) . '=' . $q->q($value));
}
}
return $this;
}
/**
* Customisable method to create an object or list of objects in the data store.
*
* @param mixed $input An array of dumped objects.
*
* @return array The array Data\DataObject objects that were created, keyed on the unique identifier.
*
* @since 1.0
* @throws \RuntimeException if there was a problem with the data source.
*/
protected function doCreate(array $input)
{
/* @var $result Data\DataSet */
$set = $this->getResultClass('sets');
$result = new $set;
$primaryKey = $this->getPrimaryKey();
foreach ($input as $k => $object)
{
try
{
// Wrap the insert in a transaction.
$this->db->transactionStart();
foreach ($this->getTables() as $tableAlias => $tableName)
{
// Ignore the joined tables.
if (isset($this->joins[$tableAlias]))
{
continue;
}
$row = $this->getTableObject($tableName, $object);
$this->db->insertObject($tableName, $row, $primaryKey);
if ($primaryKey && !isset($object->$primaryKey))
{
$object->$primaryKey = $row->$primaryKey;
}
}
$this->db->transactionCommit();
}
catch (\RuntimeException $error)
{
// Rollback the transaction.
$this->db->transactionRollback();
throw $error;
}
$result[$primaryKey ? $object->$primaryKey : $k] = new Data\DataObject($object);
}
return $result;
}
/**
* Customisable method to delete a list of objects from the data store.
*
* This method assumes that the first table is the 'master' table, and all other tables have foreign key relationships
* that will cascade the delete operation.
*
* @param array $where The criteria by which to search the data source in the form:
* array(column => value)
* If value is an array, the condition is evaluated an an "IN".
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
protected function doDelete($where)
{
if (empty($where))
{
return;
}
$tableName = array_shift($this->getTables());
$q = $this->db->getQuery(true);
$q->delete($q->qn($tableName));
$this->addFindQueryWhere($q, is_array($where) ? $where : array());
$this->db->setQuery($q)->execute();
}
/**
* Customisable method to find the primary identifiers for a list of objects from the data store based on an
* associative array of key/value pair criteria.
*
* @param array $where The criteria by which to search the data source in the form:
* array(column => value)
* If value is an array, the condition is evaluated an an "IN".
* @param array $sort An associative array defining the sort direction in the form:
* array(column => direction).
* @param integer $offset The pagination offset for the result set.
* @param integer $limit The number of results to return (zero for all).
*
* @return Data\DataSet The set of data that matches the criteria.
*
* @since 1.0
* @throws \RuntimeException
*/
protected function doFind($where = null, $sort = null, $offset = 0, $limit = 0)
{
$primaryKey = $this->getPrimaryKey();
$q = $this->db->getQuery(true);
$this->addFindQueryBase($q)
->addFindQueryWhere($q, is_array($where) ? $where : array())
->addFindQuerySort($q, is_array($sort) ? $sort : array());
$set = $this->getResultClass('sets');
$this->db->setQuery($q, $offset, $limit);
return new $set($this->db->loadObjectList($primaryKey, $this->getResultClass('objects')));
}
/**
* Customisable method to update an object or list of objects in the data store.
*
* @param mixed $input An array of dumped objects.
*
* @return array The array of Data\DataObject objects that were updated, keyed on the unique identifier.
*
* @since 1.0
* @throws \RuntimeException
*/
protected function doUpdate(array $input)
{
/* @var $result Data\DataSet */
$set = $this->getResultClass('sets');
$result = new $set;
$primaryKey = $this->getPrimaryKey();
foreach ($input as $object)
{
try
{
// Wrap the insert in a transaction.
$this->db->transactionStart();
foreach ($this->getTables() as $tableAlias => $tableName)
{
// Ignore the joined tables.
if (isset($this->joins[$tableAlias]))
{
continue;
}
$row = $this->getTableObject($tableName, $object);
$this->db->updateObject($tableName, $row, $primaryKey);
}
$result[$primaryKey] = new Data\DataObject($object);
$this->db->transactionCommit();
}
catch (\RuntimeException $error)
{
// Rollback the transaction.
$this->db->transactionRollback();
throw $error;
}
}
return $result;
}
/**
* Gets the columns to add to the SELECT clause.
*
* @param string $name The table name or alias (must match what was used in `addTable` and `setTableSelect`).
* @param string $default The default value if a custom select is not available.
*
* @return mixed
*
* @since 1.0
*/
protected function getTableSelect($name, $default)
{
if (isset($this->selects[$name]))
{
return $this->selects[$name] ?: null;
}
return $default;
}
/**
* Get the columns for a database table.
*
* This method lazy loads the column for a table into a private static array. This means that table information is loaded
* only once regardless of how many mappers are using the same table.
*
* @param string $tableName The name of the table.
*
* @return array An associative array of 'column name' => 'column type' values.
*
* @since 1.0
* @throws \RuntimeException is the table does not exist.
*/
protected function getTableColumns($tableName)
{
if (!isset(self::$columns[$tableName]))
{
self::$columns[$tableName] = $this->db->getTableColumns($tableName);
}
return self::$columns[$tableName];
}
/**
* Get the object that can be inserted into a table.
*
* This method returns an object with properties that match columns in the specified table.
*
* @param string $tableName The name of the table.
* @param string $input The input object.
*
* @return \stdClass An object
*
* @since 1.0
*/
protected function getTableObject($tableName, $input)
{
return (object) array_intersect_key((array) $input, $this->getTableColumns($tableName));
}
}
<?php
/**
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Data;
use Psr\Cache\CacheInterface;
/**
* Data source mapper class.
*
* This class is used to provide a layer between data objects and their datasource.
*
* An <em>initialise</em> method may be implemented to perform custom setup.
*
* @since 1.0
*/
abstract class DataMapper
{
/**
* A cache object.
*
* @var Cache
* @since 2.0
*/
private $cache;
/**
* The classes to assign to new results.
*
* @var array
* @since 1.0
*/
private $results;
/**
* The class constructor.
*
* @param CacheInterface $cache An optional cache object for caching `find` results.
*
* @since 1.0
*/
public function __construct(CacheInterface $cache = null)
{
$this->cache = $cache;
$this->results = array('objects' => '\Joomla\Data\DataObject', 'sets' => '\Joomla\Data\DataSet');
// Do the customisable initialisation.
$this->initialise();
}
/**
* Creates a new object or list of objects in the data store.
*
* @param Dumpable $input An object or an array of objects for the mapper to create in the data store.
*
* @return mixed The Data or Set object that was created.
*
* @since 1.0
* @throws \UnexpectedValueException if doCreate does not return an array.
*/
public function create(DumpableInterface $input)
{
$dump = $input->dump();
$objects = $this->doCreate(is_array($dump) ? $dump : array($dump));
if ($objects instanceof DataSet)
{
if (is_array($dump))
{
return $objects;
}
else
{
$objects->rewind();
return $objects->current();
}
}
throw new \UnexpectedValueException(sprintf('%s::create()->doCreate() returned %s', get_class($this), gettype($input)));
}
/**
* Deletes an object or a list of objects from the data store.
*
* @param mixed $where The criteria by which to delete data from the data source.
*
* @return void
*
* @since 1.0
* @throws \UnexpectedValueException if doDelete returned something other than an object or an array.
*/
public function delete($where)
{
$this->doDelete($where);
}
/**
* Finds a list of objects based on arbitrary criteria.
*
* @param mixed $where The criteria by which to search the data source.
* @param mixed $sort The sorting to apply to the search.
* @param integer $offset The pagination offset for the result set.
* @param integer $limit The number of results to return (zero for all).
*
* @return DataSet A set of objects matching the search criteria and pagination settings.
*
* @since 1.0
* @throws \UnexpectedValueException if Data\Mapper->doFind does not return a Data\DataSet.
*/
public function find($where = null, $sort = null, $offset = 0, $limit = 0)
{
/* if ($this->hasCache())
{
$cacheKey = $this->getCacheKey(array($where, $sort, (int) $offset, (int) $limit));
$cacheItem = $this->getCache()->get($cacheKey);
if ($cacheItem->isHit())
{
return $cacheItem->getValue();
}
} */
// Find the appropriate results based on the critera.
$objects = $this->doFind($where, $sort, $offset, $limit);
if ($objects instanceof DataSet)
{
// The doFind method should honour the limit, but let's check just in case.
if ($limit > 0 && count($objects) > $limit)
{
$count = 1;
foreach ($objects as $k => $v)
{
if ($count > $limit)
{
unset($objects[$k]);
}
$count ++;
}
}
/* if ($this->hasCache())
{
$this->getCache()->set($cacheKey, $objects);
} */
return $objects;
}
throw new \UnexpectedValueException(sprintf('%s->doFind cannot return a %s', __METHOD__, gettype($objects)));
}
/**
* Finds a single object based on arbitrary criteria.
*
* @param mixed $where The criteria by which to search the data source.
* @param mixed $sort The sorting to apply to the search.
*
* @return Object An object matching the search criteria, or null if none found.
*
* @since 1.0
* @throws \UnexpectedValueException if Data\Mapper->doFind (via Data\Mapper->find) does not return a Data\DataSet.
*/
public function findOne($where = null, $sort = null)
{
// Find the appropriate results based on the critera.
$objects = $this->find($where, $sort, 0, 1);
// Check the results (empty doesn't work on \Joomla\Data\DataSet).
if (count($objects) == 0)
{
// Should we throw an exception?
return null;
}
// Load the object from the first element of the array (emulates array_shift on an ArrayAccess object).
$objects->rewind();
return $objects->current();
}
/**
* Sets the result classes for data objects and data set.
*
* By default, a mapper should create Joomla\Data\DataObject objects and Joomla\Data\DataSet containers of objects.
* You can modify the actual classes returned by mapper methods by using this method.
*
* @param string $objects The name of the class of objects that are returns in a data set (defaults to Joomla\Data\DataObject).
* @param string $sets The name of the class of for data sets that are returned (defaults to Joomla\Data\DataSet).
*
* @return Mapper Returns itself to support chaining.
*
* @since 1.0
* @throws \InvalidArgumentException if the classes are not derived from Object or Set.
*/
public function setResultClasses($objects = null, $sets = null)
{
if ($objects !== null)
{
if (is_a($objects, '\Joomla\Data\DataObject', true) || is_subclass_of($objects, '\Joomla\Data\DataObject'))
{
$this->results['objects'] = $objects;
}
else
{
throw new \InvalidArgumentException(sprintf('%s is not a subclass of Joomla\Data\DataObject in %s', $objects, __METHOD__));
}
}
if ($sets !== null)
{
if (is_a($sets, '\Joomla\Data\DataSet', true) || is_subclass_of($sets, '\Joomla\Data\DataSet'))
{
$this->results['sets'] = $sets;
}
else
{
throw new \InvalidArgumentException(sprintf('%s is not a subclass of Joomla\Data\DataSet in %s', $sets, __METHOD__));
}
}
return $this;
}
/**
* Updates an object or a list of objects in the data store.
*
* @param Dumpable $input An object or a list of objects to update.
*
* @return mixed The object or object list updated.
*
* @since 1.0
* @throws \UnexpectedValueException if doUpdate returned something unexpected.
*/
public function update(DumpableInterface $input)
{
$dump = $input->dump();
$objects = $this->doUpdate(is_array($dump) ? $dump : array($dump));
if ($objects instanceof DataSet)
{
if (is_array($dump))
{
return $objects;
}
else
{
$objects->rewind();
return $objects->current();
}
}
throw new \UnexpectedValueException(sprintf('%s::update()->doUpdate() returned %s', get_class($this), gettype($objects)));
}
/**
* Customisable method to create an object or list of objects in the data store.
*
* @param mixed $input An array of dumped objects.
*
* @return array The array Data objects that were created, keyed on the unique identifier.
*
* @since 1.0
* @throws \RuntimeException if there was a problem with the data source.
*/
abstract protected function doCreate(array $input);
/**
* Customisable method to delete a list of objects from the data store.
*
* @param mixed $where The criteria by which to delete data from the data source.
*
* @return void
*
* @since 1.0
* @throws \RuntimeException
*/
abstract protected function doDelete($where);
/**
* Customisable method to find the primary identifiers for a list of objects from the data store based on an
* associative array of key/value pair criteria.
*
* @param mixed $where The criteria by which to search the data source.
* @param mixed $sort The sorting to apply to the search.
* @param integer $offset The pagination offset for the result set.
* @param integer $limit The number of results to return (zero for all).
*
* @return Set The set of data that matches the criteria.
*
* @since 1.0
* @throws \RuntimeException
*/
abstract protected function doFind($where = null, $sort = null, $offset = 0, $limit = 0);
/**
* Customisable method to update an object or list of objects in the data store.
*
* @param mixed $input An array of dumped objects.
*
* @return array The array of Data objects that were updated, keyed on the unique identifier.
*
* @since 1.0
* @throws \RuntimeException
*/
abstract protected function doUpdate(array $input);
/**
* Gets the internal cache object for the mapper.
*
* @return CacheInterface
*
* @since 1.0
*/
protected function getCache()
{
return $this->cache;
}
/**
* Gets the unique caching key for a given token.
*
* @param mixed $token A token value.
*
* @return string
*
* @since 1.0
*/
protected function getCacheKey($token)
{
return md5(__CLASS__ . serialize($token));
}
/**
* Gets the result class for a type of result.
*
* This method is used to get the class for either an object returned by the mapper, or a data set returned by the mapper.
* Custom mappers should use this method when returning results.
*
* @param string $type The type of result. Valid values are 'objects' or 'sets'.
*
* @return string
*
* @since 1.0
* @throws \InvalidArgumentException if the result type is invalid.
*/
protected function getResultClass($type)
{
if (isset($this->results[$type]))
{
return $this->results[$type];
}
throw new \InvalidArgumentException(sprintf('%s is not a valid result type in %s', $type, __METHOD__));
}
/**
* Checks if the internal cache object is set and/or enabled.
*
* @return boolean
*
* @since 1.0
*/
protected function hasCache()
{
return isset($this->cache);
}
/**
* Customisable initialise method for extended classes to use.
*
* This method is called last in the Data\Mapper constructor.
*
* @return void
*
* @codeCoverageIgnore
* @since 1.0
*/
protected function initialise()
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment