silverstripe-framework/src/ORM/RelationList.php

163 lines
4.7 KiB
PHP

<?php
namespace SilverStripe\ORM;
use Exception;
use Sminnee\CallbackList\CallbackList;
/**
* A DataList that represents a relation.
*
* Adds the notion of a foreign ID that can be optionally set.
*/
abstract class RelationList extends DataList implements Relation
{
/**
* @var CallbackList|null
*/
protected $addCallbacks;
/**
* @var CallbackList|null
*/
protected $removeCallbacks;
/**
* Manage callbacks which are called after the add() action is completed.
* Each callback will be passed (RelationList $this, DataObject|int $item, array $extraFields).
* If a relation methods is manually defined, this can be called to adjust the behaviour
* when adding records to this list.
*
* Needs to be defined through an overloaded relationship getter
* to ensure it is set consistently. These getters return a new object
* every time they're called.
*
* Note that subclasses of RelationList must implement the callback for it to function
*
* @return this
*/
public function addCallbacks(): CallbackList
{
if (!$this->addCallbacks) {
$this->addCallbacks = new CallbackList();
}
return $this->addCallbacks;
}
/**
* Manage callbacks which are called after the remove() action is completed.
* Each Callback will be passed (RelationList $this, [int] $removedIds).
*
* Needs to be defined through an overloaded relationship getter
* to ensure it is set consistently. These getters return a new object
* every time they're called. Example:
*
* ```php
* class MyObject extends DataObject()
* {
* private static $many_many = [
* 'MyRelationship' => '...',
* ];
* public function MyRelationship()
* {
* $list = $this->getManyManyComponents('MyRelationship');
* $list->removeCallbacks()->add(function ($removedIds) {
* // ...
* });
* return $list;
* }
* }
* ```
*
* If a relation methods is manually defined, this can be called to adjust the behaviour
* when adding records to this list.
*
* Subclasses of RelationList must implement the callback for it to function
*
* @return this
*/
public function removeCallbacks(): CallbackList
{
if (!$this->removeCallbacks) {
$this->removeCallbacks = new CallbackList();
}
return $this->removeCallbacks;
}
/**
* Any number of foreign keys to apply to this list
*
* @return string|array|null
*/
public function getForeignID()
{
return $this->dataQuery->getQueryParam('Foreign.ID');
}
public function getQueryParams()
{
$params = parent::getQueryParams();
// Remove `Foreign.` query parameters for created objects,
// as this would interfere with relations on those objects.
foreach (array_keys($params ?? []) as $key) {
if (stripos($key ?? '', 'Foreign.') === 0) {
unset($params[$key]);
}
}
return $params;
}
/**
* Returns a copy of this list with the ManyMany relationship linked to
* the given foreign ID.
*
* @param int|array $id An ID or an array of IDs.
*
* @return static
*/
public function forForeignID($id)
{
// Turn a 1-element array into a simple value
if (is_array($id) && sizeof($id ?? []) == 1) {
$id = reset($id);
}
// Calculate the new filter
$filter = $this->foreignIDFilter($id);
$list = $this->alterDataQuery(function (DataQuery $query) use ($id, $filter) {
// Check if there is an existing filter, remove if there is
$currentFilter = $query->getQueryParam('Foreign.Filter');
if ($currentFilter) {
try {
$query->removeFilterOn($currentFilter);
} catch (Exception $e) {
/* NOP */
}
}
// Add the new filter
$query->setQueryParam('Foreign.ID', $id);
$query->setQueryParam('Foreign.Filter', $filter);
$query->where($filter);
});
return $list;
}
/**
* Returns a where clause that filters the members of this relationship to
* just the related items.
*
*
* @param array|integer $id (optional) An ID or an array of IDs - if not provided, will use the current ids as
* per getForeignID
* @return array Condition In array(SQL => parameters format)
*/
abstract protected function foreignIDFilter($id = null);
}