Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
88.00% |
22 / 25 |
CRAP | |
93.69% |
104 / 111 |
Fields | |
0.00% |
0 / 1 |
|
88.00% |
22 / 25 |
55.76 | |
93.69% |
104 / 111 |
__construct(array $fields = null, UserInterface $user = null) | |
0.00% |
0 / 1 |
6.73 | |
72.73% |
8 / 11 |
|||
getIterator() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
count() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
offsetSet($offset, $value) | |
100.00% |
1 / 1 |
5 | |
100.00% |
10 / 10 |
|||
offsetGet($offset) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
offsetExists($offset) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
offsetUnset($offset) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setUser(UserInterface $user) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getUser() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
has($id) | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
get($id) | |
100.00% |
1 / 1 |
3 | |
100.00% |
5 / 5 |
|||
add($field, $modelName = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
prepend($field, $modelName = null) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
append($field, $modelName = null) | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
prepareFieldForAdding($field, $modelName = null) | |
100.00% |
1 / 1 |
4 | |
100.00% |
12 / 12 |
|||
remove($id) | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
getAll($filters = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getVisibleFields($filters = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getSortableFields($filters = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getEditableFields($filters = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getFilterableFields($filters = null) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getModelsByName() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
handleModelsForDbField(DbField $field, $modelName) | |
100.00% |
1 / 1 |
8 | |
100.00% |
23 / 23 |
|||
getFieldsPassingMethodCheck($fieldMethodName, $filters) | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
applyFilters(Fields $fields, $filters) | |
100.00% |
1 / 1 |
4 | |
100.00% |
9 / 9 |
<?php | |
/** | |
* Dewdrop | |
* | |
* @link https://github.com/DeltaSystems/dewdrop | |
* @copyright Delta Systems (http://deltasys.com) | |
* @license https://github.com/DeltaSystems/dewdrop/LICENSE | |
*/ | |
namespace Dewdrop; | |
use ArrayAccess; | |
use Countable; | |
use IteratorAggregate; | |
use Dewdrop\Db\Field as DbField; | |
use Dewdrop\Fields\Field as CustomField; | |
use Dewdrop\Fields\FieldInterface; | |
use Dewdrop\Fields\FieldsIterator; | |
use Dewdrop\Fields\Filter\FilterInterface; | |
use Dewdrop\Fields\UserInterface; | |
/** | |
* The Fields API is at the core of many of Dewdrop's abstractions. It has two | |
* primary goals: | |
* | |
* 1) Leverage metadata from the database (e.g. information about various | |
* constraints, data-types, etc.) to make working with database fields in | |
* various contexts simpler and less error-prone. | |
* | |
* 2) To allow the definition of non-DB-related fields as well, so that the | |
* Fields API can be used to inject customizable pieces of code into | |
* logic and rendering loops in a clean way. | |
* | |
* Adding fields is possible in a few different ways. You can add a DB field | |
* directly from a \Dewdrop\Db\Table model: | |
* | |
* <pre> | |
* $fields->add($model->field('my_field')); | |
* </pre> | |
* | |
* You can add a custom field by passing an ID string to the add() method and | |
* then customizing the field: | |
* | |
* <pre> | |
* $fields->add('my_custom_field_id') | |
* ->setLabel('Just a Custom Field') | |
* ->setVisible(true) | |
* ->assignHelperCallback( | |
* 'TableCell.Content', | |
* function ($helper, array $rowData) { | |
* return 'Hello, world'; | |
* } | |
* ); | |
* </pre> | |
* | |
* Or, you can instantiate and add the field object directly: | |
* | |
* <pre> | |
* $field = new \Dewdrop\Fields\Field(); | |
* | |
* $field | |
* ->setId('my_custom_field_id') | |
* ->setLabel('Just a Custom Field') | |
* ->setVisible(true) | |
* ->assignHelperCallback( | |
* 'TableCell.Content', | |
* function ($helper, array $rowData) { | |
* return 'Hello, world'; | |
* } | |
* ); | |
* | |
* $fields->add($field); | |
* </pre> | |
* | |
* Once added, you can get your fields back in a number of different ways: | |
* | |
* 1) The DOM-like get(), has(), and remove() methods all take a field ID. | |
* | |
* 2) The getAll(), getVisibleFields(), getSortableFields(), getFilterableFields() | |
* and getEditableFields() objects all will return new \Dewdrop\Fields objects | |
* that contain only the fields allowed by the method you called. When calling | |
* any of this methods, you can pass any number of \Dewdrop\Fields\Filter | |
* objects as well to further sort or limit the fields you get back. | |
* | |
* 3) You can iterate over the Fields object just like an array. | |
* | |
* Many other objects in Dewdrop can receive a Fields collection and use it | |
* to make decisions. For example, notable view helpers such as Table can use | |
* a collection of fields to render their headers and cells. | |
* | |
* @see \Dewdrop\Fields\Helper\HelperAbstract | |
*/ | |
class Fields implements ArrayAccess, IteratorAggregate, Countable | |
{ | |
/** | |
* The fields currently contained in this collection. | |
* | |
* @var array | |
*/ | |
private $fields = array(); | |
/** | |
* The model instances associated with added DB fields. | |
* | |
* @var array | |
*/ | |
private $modelInstances = array(); | |
/** | |
* The models associated with added DB fields by name (typically the table | |
* name, but could be different if a custom name is provided when calling | |
* add()). | |
* | |
* @var array | |
*/ | |
private $modelsByName = array(); | |
/** | |
* An object implementing the \Dewdrop\Fields\UserInterface interface, | |
* which can be used to take advantage of the authorization features | |
* in the \Dewdrop\Fields API. | |
*/ | |
private $user; | |
/** | |
* Optionally supply an array of fields that can be used as an initial | |
* set for this collection. | |
* | |
* @param array $fields | |
* @param UserInterface $user | |
*/ | |
public function __construct(array $fields = null, UserInterface $user = null) | |
{ | |
if (is_array($fields)) { | |
foreach ($fields as $field) { | |
$this->add($field); | |
} | |
} | |
if (null !== $user) { | |
$this->user = $user; | |
} elseif (Pimple::hasResource('user') && Pimple::getResource('user') instanceof UserInterface) { | |
$this->user = Pimple::getResource('user'); | |
} | |
} | |
/** | |
* Iterate over this Fields set using a FieldsIterator. You typically don't | |
* call this directly, you just do a foreach over the Fields object. | |
* | |
* @return FieldsIterator | |
*/ | |
public function getIterator() | |
{ | |
return new FieldsIterator($this->fields); | |
} | |
/** | |
* Count the number of fields in this collection. | |
* | |
* @return integer | |
*/ | |
public function count() | |
{ | |
return count($this->fields); | |
} | |
/** | |
* Allow addition/replacement of Field objects on this collection via array | |
* syntax: | |
* | |
* <pre> | |
* $fields['id'] = $field; | |
* </pre> | |
* | |
* This is part of the ArrayAccess interface built into PHP. | |
* | |
* @param string $offset | |
* @param mixed $value | |
* @throws Exception | |
* @return void | |
*/ | |
public function offsetSet($offset, $value) | |
{ | |
if (!$value instanceof FieldInterface) { | |
throw new Exception('\Dewdrop\Fields only excepts field objects'); | |
} | |
if (is_string($offset) && !is_numeric($offset)) { | |
$value->setId($offset); | |
if ($this->has($offset)) { | |
$this->remove($offset); | |
} | |
} | |
$this->add($value); | |
} | |
/** | |
* Get a field by its ID using ArrayAccess syntax. | |
* | |
* <pre> | |
* echo $fields['id']->getLabel(); | |
* </pre> | |
* | |
* This method is part of the ArrayAccess interface built into PHP. | |
* | |
* @param string $offset | |
* @return mixed | |
*/ | |
public function offsetGet($offset) | |
{ | |
return $this->get($offset); | |
} | |
/** | |
* Test to see if the specified field ID exists in this Fields collection | |
* using isset(): | |
* | |
* <pre> | |
* isset($fields['id']); | |
* </pre> | |
* | |
* This method is part of the ArrayAccess interface built into PHP. | |
* | |
* @param string $offset | |
* @return boolean | |
*/ | |
public function offsetExists($offset) | |
{ | |
return $this->has($offset); | |
} | |
/** | |
* Allow removal of a field via ArrayAccess syntax: | |
* | |
* <pre> | |
* unset($fields['id']); | |
* </pre> | |
* | |
* @param string $offset | |
* @return void | |
*/ | |
public function offsetUnset($offset) | |
{ | |
$this->remove($offset); | |
} | |
/** | |
* Set the UserInterface object that can be used by the | |
* authorization-related features in \Dewdrop\Fields. | |
* | |
* @param UserInterface $user | |
* @return Fields | |
*/ | |
public function setUser(UserInterface $user) | |
{ | |
$this->user = $user; | |
return $this; | |
} | |
/** | |
* Get the user object associated with these fields. | |
* | |
* @return UserInterface | |
*/ | |
public function getUser() | |
{ | |
return $this->user; | |
} | |
/** | |
* Check to see if a field with the given ID exists in this collection. | |
* | |
* @param string $id | |
* @return boolean | |
*/ | |
public function has($id) | |
{ | |
/* @var $field FieldInterface */ | |
foreach ($this->fields as $field) { | |
if ($field->getId() === $id) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Get the field matching the supplied ID from this collection. | |
* | |
* @param string $id | |
* @return FieldInterface | |
*/ | |
public function get($id) | |
{ | |
/* @var $field FieldInterface */ | |
foreach ($this->fields as $field) { | |
if ($field->getId() === $id) { | |
return $field; | |
} | |
} | |
return null; | |
} | |
/** | |
* Add the supplied field to this collection. Can be a FieldInterface | |
* object or a string, in which case a new custom field will be added | |
* with the supplied string as its ID. | |
* | |
* The newly added FieldInterface object is returned from this method | |
* so that it can be further customized immediately, using method | |
* chaining. Once you've completed calling methods on the FieldInterface | |
* object itself, you can call any \Dewdrop\Fields methods to return | |
* execution to that context. | |
* | |
* @param mixed $field | |
* @param string $modelName | |
* @throws Exception | |
* @return FieldInterface | |
*/ | |
public function add($field, $modelName = null) | |
{ | |
$field = $this->prepareFieldForAdding($field, $modelName); | |
$this->fields[] = $field; | |
return $field; | |
} | |
/** | |
* Prepend the supplied field (or custom field ID) to the field set. | |
* | |
* @param mixed $field | |
* @param string|null $modelName | |
* @return FieldInterface | |
*/ | |
public function prepend($field, $modelName = null) | |
{ | |
$field = $this->prepareFieldForAdding($field, $modelName); | |
array_unshift($this->fields, $field); | |
return $field; | |
} | |
/** | |
* An alias to add(). Just here because it's odd to have prepend() | |
* and not append(). | |
* | |
* @param mixed $field | |
* @param string|null $modelName | |
* @return FieldInterface | |
*/ | |
public function append($field, $modelName = null) | |
{ | |
return $this->add($field, $modelName); | |
} | |
/** | |
* Prepare to add a field for the supplied arguments. If $field is a string | |
* we'll create a new CustomField. Otherwise, we'll just directly add the | |
* field object itself. | |
* | |
* @param mixed $field | |
* @param string|null $modelName | |
* @return FieldInterface | |
* @throws Exception | |
*/ | |
private function prepareFieldForAdding($field, $modelName = null) | |
{ | |
if (is_string($field)) { | |
$id = $field; | |
$field = new CustomField(); | |
$field->setId($id); | |
} | |
if (!$field instanceof FieldInterface) { | |
throw new Exception('Field must be a string or instance of \Dewdrop\Fields\FieldInterface'); | |
} | |
if ($field instanceof DbField) { | |
$this->handleModelsForDbField($field, $modelName); | |
} | |
$field->setFieldsSet($this); | |
return $field; | |
} | |
/** | |
* Remove the field with the given ID from this collection. | |
* | |
* @param string $id | |
* @return Fields | |
*/ | |
public function remove($id) | |
{ | |
/* @var $field FieldInterface */ | |
foreach ($this->fields as $index => $field) { | |
if ($field->getId() === $id) { | |
unset($this->fields[$index]); | |
break; | |
} | |
} | |
$this->fields = array_values($this->fields); | |
return $this; | |
} | |
/** | |
* Get all the fields currently in this collection. | |
* | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
public function getAll($filters = null) | |
{ | |
return $this->applyFilters($this, $filters); | |
} | |
/** | |
* Get any fields that are visible and pass the supplied filters. Note that | |
* you can either pass a single \Dewdrop\Fields\Filter or an array of them. | |
* | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
public function getVisibleFields($filters = null) | |
{ | |
return $this->getFieldsPassingMethodCheck('isVisible', $filters); | |
} | |
/** | |
* Get any fields that are sortable and pass the supplied filters. Note that | |
* you can either pass a single \Dewdrop\Fields\Filter or an array of them. | |
* | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
public function getSortableFields($filters = null) | |
{ | |
return $this->getFieldsPassingMethodCheck('isSortable', $filters); | |
} | |
/** | |
* Get any fields that are editable and pass the supplied filters. Note that | |
* you can either pass a single \Dewdrop\Fields\Filter or an array of them. | |
* | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
public function getEditableFields($filters = null) | |
{ | |
return $this->getFieldsPassingMethodCheck('isEditable', $filters); | |
} | |
/** | |
* Get any fields that are filterable and pass the supplied filters. Note that | |
* you can either pass a single \Dewdrop\Fields\Filter or an array of them. | |
* | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
public function getFilterableFields($filters = null) | |
{ | |
return $this->getFieldsPassingMethodCheck('isFilterable', $filters); | |
} | |
/** | |
* Get all the model instances associated with added DB fields, indexed by | |
* name. | |
* | |
* @return array | |
*/ | |
public function getModelsByName() | |
{ | |
return $this->modelsByName; | |
} | |
/** | |
* Handle the model (\Dewdrop\Db\Table) objects for the supplied newly added | |
* DB field. We allow custom model names for situations where you need to | |
* add fields from two different instances of the same model (e.g. you have | |
* an two Addresses model instances on your fields set because you have both | |
* a billing and a shipping address). | |
* | |
* @param DbField $field | |
* @param string $modelName | |
* @throws Exception | |
*/ | |
protected function handleModelsForDbField(DbField $field, $modelName) | |
{ | |
$fieldTable = $field->getTable(); | |
$groupName = $field->getGroupName(); | |
if (null === $modelName && | |
isset($this->modelInstances[$groupName]) && | |
$this->modelInstances[$groupName] !== $fieldTable | |
) { | |
throw new Exception( | |
'When adding fields from two instances of the same model, you must specify ' | |
. 'an alternate model name as the second paramter to add().' | |
); | |
} | |
if (null === $modelName) { | |
$modelName = $groupName; | |
} | |
if (isset($this->modelsByName[$modelName]) && | |
$this->modelsByName[$modelName] !== $fieldTable | |
) { | |
throw new Exception( | |
"The name '{$modelName}' has already been used with another model instance. " | |
. 'Please make sure to use model names consistently when adding fields.' | |
); | |
} | |
$this->modelInstances[$groupName] = $fieldTable; | |
$this->modelsByName[$modelName] = $fieldTable; | |
// Update the field's control name so that generated IDs, etc., use the new name | |
if ($modelName !== $groupName) { | |
$field->setGroupName($modelName); | |
} | |
} | |
/** | |
* Get any fields that return true when the supplied method is called and pass | |
* the supplied filters. Note that you can either pass a single | |
* \Dewdrop\Fields\Filter or an array of them. | |
* | |
* @param string $fieldMethodName | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
protected function getFieldsPassingMethodCheck($fieldMethodName, $filters) | |
{ | |
$fields = new Fields([], $this->user); | |
foreach ($this->fields as $field) { | |
if ($field->$fieldMethodName($this->user)) { | |
$fields->add($field); | |
} | |
} | |
return $this->applyFilters($fields, $filters); | |
} | |
/** | |
* Apply the supplied filters to the Fields. You can pass no filters, | |
* a single filter, or an array of filters. | |
* | |
* @param Fields $fields | |
* @param mixed $filters | |
* @return Fields | |
*/ | |
protected function applyFilters(Fields $fields, $filters) | |
{ | |
if (!$filters) { | |
return $fields; | |
} | |
if (!is_array($filters)) { | |
$filters = array($filters); | |
} | |
/* @var $filter FilterInterface */ | |
foreach ($filters as $filter) { | |
$fields = $filter->apply($fields); | |
} | |
return $fields; | |
} | |
} |