Skip to content

Commit

Permalink
(REF) APIv4 - Extract CheckRequiredTrait
Browse files Browse the repository at this point in the history
This is an incremental step towards moving the 'required fields check' to be
an event/subscriber (`civi.api4.validate`).

Before: `AbstractAction::checkRequiredFields()` is a helper method on all
action classes. However, it is only used by two classes.

After: `CheckRequiredTrait` is a helper that can be used to mix-in
`checkRequired()` for a couple classes.

Why: The aim is to move this to its own subscriber, but that's a bit more
work right now.  This isolates the functional so that it's less likely to
get accidental usage and so that it's easier to see the lines around it.
  • Loading branch information
totten committed May 27, 2021
1 parent 4acb9c2 commit 61aa344
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 39 deletions.
24 changes: 0 additions & 24 deletions Civi/Api4/Generic/AbstractAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -456,30 +456,6 @@ public function reflect() {
return $this->_reflection;
}

/**
* Validates required fields for actions which create a new object.
*
* @param $values
* @return array
* @throws \API_Exception
*/
protected function checkRequiredFields($values) {
$unmatched = [];
foreach ($this->entityFields() as $fieldName => $fieldInfo) {
if (!isset($values[$fieldName]) || $values[$fieldName] === '') {
if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) {
$unmatched[] = $fieldName;
}
elseif (!empty($fieldInfo['required_if'])) {
if ($this->evaluateCondition($fieldInfo['required_if'], ['values' => $values])) {
$unmatched[] = $fieldName;
}
}
}
}
return $unmatched;
}

/**
* Replaces pseudoconstants in input values
*
Expand Down
8 changes: 3 additions & 5 deletions Civi/Api4/Generic/AbstractCreateAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*/
abstract class AbstractCreateAction extends AbstractAction {

use \Civi\Api4\Generic\Traits\CheckRequiredTrait;

/**
* Field values to set for the new $ENTITY.
*
Expand Down Expand Up @@ -61,11 +63,7 @@ public function addValue(string $fieldName, $value) {
* @throws \API_Exception
*/
protected function validateValues() {
// FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception?
$unmatched = $this->checkRequiredFields($this->getValues());
if ($unmatched) {
throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]);
}
$this->checkRequired([$this->values]);
$e = new ValidateValuesEvent($this, [$this->getValues()], new \CRM_Utils_LazyArray(function () {
return [['old' => NULL, 'new' => $this->getValues()]];
}));
Expand Down
16 changes: 6 additions & 10 deletions Civi/Api4/Generic/AbstractSaveAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
*/
abstract class AbstractSaveAction extends AbstractAction {

use \Civi\Api4\Generic\Traits\CheckRequiredTrait;

/**
* Array of $ENTITIES to save.
*
Expand Down Expand Up @@ -95,16 +97,10 @@ public function __construct($entityName, $actionName, $idField = 'id') {
* @throws \API_Exception
*/
protected function validateValues() {
// FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception?
$unmatched = [];
foreach ($this->records as $record) {
if (empty($record[$this->idField])) {
$unmatched = array_unique(array_merge($unmatched, $this->checkRequiredFields($record)));
}
}
if ($unmatched) {
throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]);
}
$this->checkRequired($this->records, function($record) {
return empty($record[$this->idField]);
});

$e = new ValidateValuesEvent($this, $this->records, new \CRM_Utils_LazyArray(function() {
$existingIds = array_column($this->records, $this->idField);
$existing = civicrm_api4($this->getEntityName(), 'get', [
Expand Down
68 changes: 68 additions & 0 deletions Civi/Api4/Generic/Traits/CheckRequiredTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
namespace Civi\Api4\Generic\Traits;

/**
* The CheckRequiredTrait provides a helper, `checkRequired()`, to determine if required fields are specified.
* This would be consulted when adding a new record.
*
* This should be moved to a subscriber ('civi.api4.validate') that reports issues via `addError()`. However, for the moment, it
* relies on things which are in the action class (e.g. entityFields(), evaluateCondition(), $isNew<=>$idField), and it can't
* be fully extracted until those are changed.
*
* @deprecated
*/
trait CheckRequiredTrait {

abstract protected static function getEntityName();

abstract protected static function getActionName();

abstract protected function entityFields();

abstract protected function evaluateCondition($expr, $vars);

/**
* When creating new records, ensure that the 'required' fields are present. Throws an exception if any fields are missing.
*
* @param array $records
* @param callable|TRUE $isNew A function that can distinguish whether the record is new, or constant TRUE if all records are new.
* @throws \API_Exception
*/
protected function checkRequired($records, $isNew = TRUE) {
$unmatched = [];

foreach ($records as $record) {
if ($isNew === TRUE || $isNew($record)) {
$unmatched = array_unique(array_merge($unmatched, $this->checkRequiredFieldValues($record)));
}
}
if ($unmatched) {
throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]);
}
}

/**
* Validates required fields for actions which create a new object.
*
* @param $values
* @return array
* @throws \API_Exception
*/
protected function checkRequiredFieldValues($values) {
$unmatched = [];
foreach ($this->entityFields() as $fieldName => $fieldInfo) {
if (!isset($values[$fieldName]) || $values[$fieldName] === '') {
if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) {
$unmatched[] = $fieldName;
}
elseif (!empty($fieldInfo['required_if'])) {
if ($this->evaluateCondition($fieldInfo['required_if'], ['values' => $values])) {
$unmatched[] = $fieldName;
}
}
}
}
return $unmatched;
}

}

0 comments on commit 61aa344

Please sign in to comment.