Skip to content

Commit

Permalink
Afform - Simplify postprocess event, cleanup api code & improve test …
Browse files Browse the repository at this point in the history
…coverage

- Simplifies getEntityWeights function using topological sorting library
- Consolodates postprocess event listners to just processGenericEntity
- Adds missing getters/setters to api classes
- Improves the AfformSubmitEvent interface to make it easeier to use
- Adds to tests
  • Loading branch information
colemanw committed May 21, 2021
1 parent 9d4f6fb commit 5dedaa3
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 205 deletions.
19 changes: 15 additions & 4 deletions ext/afform/core/Civi/Afform/Event/AfformBaseEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@

use Symfony\Component\EventDispatcher\Event;

class AfformBaseEvent extends Event {
abstract class AfformBaseEvent extends Event {

/**
* @var array
* The main 'Afform' record/configuration.
*/
public $afform;
private $afform;

/**
* @var \Civi\Afform\FormDataModel
*/
public $formDataModel;
private $formDataModel;

/**
* @var \Civi\Api4\Generic\AbstractAction
*/
public $apiRequest;
private $apiRequest;

/**
* AfformBaseEvent constructor.
Expand All @@ -33,6 +33,17 @@ public function __construct(array $afform, \Civi\Afform\FormDataModel $formDataM
$this->apiRequest = $apiRequest;
}

public function getAfform(): array {
return $this->afform;
}

/**
* @return \Civi\Afform\FormDataModel
*/
public function getFormDataModel(): \Civi\Afform\FormDataModel {
return $this->formDataModel;
}

/**
* @return \Civi\Api4\Generic\AbstractAction
*/
Expand Down
72 changes: 43 additions & 29 deletions ext/afform/core/Civi/Afform/Event/AfformSubmitEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,45 @@
* Class AfformSubmitEvent
* @package Civi\Afform\Event
*
* Handle submission of an "<af-form>".
* Listeners ought to take any recognized items from `entityValues`, handle
* them, and remove them.
* Handle submission of an "<af-form>" entity.
*
* NOTE: I'm on the fence about whether to expose the arrays or more targeted
* methods. For the moment, this is only expected to be used internally,
* so KISS.
* The default handler of this event is `Submit::processGenericEntity`
* If special processing for an entity type is desired, add a new listener with a higher priority
* than 0, and either manipulate the $records and allow the default listener to perform the save,
* or fully process the save and cancel event propagation to bypass `processGenericEntity`.
*/
class AfformSubmitEvent extends AfformBaseEvent {

/**
* One or more records to be saved for this entity.
* (because of `<af-repeat>` all entities are treated as if they may be multi)
* @var array
* Values to be saved for this entity
*
*/
public $values;
public $records;

/**
* @var string
* entityType
*/
public $entityType;
private $entityType;

/**
* @var string
* entityName e.g. Individual1, Activity1,
*/
public $entityName;
private $entityName;

/**
* Ids of each saved entity.
*
* Each key in the array corresponds to the name of an entity,
* and the value is an array of ids
* (because of `<af-repeat>` all entities are treated as if they may be multi)
* E.g. $entityIds['Individual1'] = [1];
*
* @var array
* List of Submitted Entities and their matching ids
* $entityIds['Individual1'] = 1;
*/
public $entityIds;
private $entityIds;

/**
* AfformSubmitEvent constructor.
Expand All @@ -55,36 +59,46 @@ class AfformSubmitEvent extends AfformBaseEvent {
* @param string $entityName
* @param array $entityIds
*/
public function __construct(array $afform, FormDataModel $formDataModel, Submit $apiRequest, $values, string $entityType, string $entityName, array &$entityIds) {
public function __construct(array $afform, FormDataModel $formDataModel, Submit $apiRequest, &$values, string $entityType, string $entityName, array &$entityIds) {
parent::__construct($afform, $formDataModel, $apiRequest);
$this->values = $values;
$this->records =& $values;
$this->entityType = $entityType;
$this->entityName = $entityName;
$this->entityIds = $entityIds;
$this->entityIds =& $entityIds;
}

/**
* Set the entity type associated with this event
* @param string $entityType
* Get the entity type associated with this event
* @return string
*/
public function setEntityType(string $entityType): void {
$this->entityType = $entityType;
public function getEntityType(): string {
return $this->entityType;
}

/**
* Set the values associated with this event
* @param array $values
* Get the entity name associated with this event
* @return string
*/
public function setValues(array $values): void {
$this->values = $values;
public function getEntityName(): string {
return $this->entityName;
}

/**
* Set the entity name associated with this event
* @param string $entityName
* @return callable
* API4-style
*/
public function setEntityName(string $entityName): void {
$this->entityName = $entityName;
public function getSecureApi4() {
return $this->getFormDataModel()->getSecureApi4($this->entityName);
}

/**
* @param $index
* @param $entityId
* @return $this
*/
public function setEntityId($index, $entityId) {
$this->entityIds[$this->entityName][$index] = $entityId;
return $this;
}

}
9 changes: 8 additions & 1 deletion ext/afform/core/Civi/Afform/FormDataModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,18 @@ protected function parseFields($nodes, $entity = NULL, $join = NULL) {
}

/**
* @return array
* @return array[]
* Ex: $entities['spouse']['type'] = 'Contact';
*/
public function getEntities() {
return $this->entities;
}

/**
* @return array
*/
public function getEntity($entityName) {
return $this->entities[$entityName] ?? NULL;
}

}
46 changes: 21 additions & 25 deletions ext/afform/core/Civi/Afform/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,34 @@
*/
class Utils {

/**
* Sorts entities according to references to each other
*
* Returns a list of entity names in order of when they should be processed,
* so that an entity being referenced is saved before the entity referencing it.
*
* @param $formEntities
* @param $entityValues
* @return string[]
*/
public static function getEntityWeights($formEntities, $entityValues) {
$entityWeights = $entityMapping = $entitiesToBeProcessed = [];
$sorter = new \MJS\TopSort\Implementations\FixedArraySort();

foreach ($formEntities as $entityName => $entity) {
$entityWeights[$entityName] = 1;
$entityMapping[$entityName] = $entity['type'];
foreach ($entityValues[$entity['type']][$entityName] as $record) {
foreach ($record as $index => $vals) {
foreach ($vals as $field => $value) {
if (array_key_exists($value, $entityWeights)) {
$entityWeights[$entityName] = max((int) $entityWeights[$entityName], (int) ($entityWeights[$value] + 1));
}
else {
if (!array_key_exists($value, $entitiesToBeProcessed)) {
$entitiesToBeProcessed[$value] = [$entityName];
}
else {
$entitiesToBeProcessed[$value][] = $entityName;
}
$references = [];
foreach ($entityValues[$entityName] as $record) {
foreach ($record['fields'] as $fieldName => $fieldValue) {
foreach ((array) $fieldValue as $value) {
if (array_key_exists($value, $formEntities) && $value !== $entityName) {
$references[$value] = $value;
}
}
}
}
// If any other entities have been processed that relied on this entity lets now alter their weights based on this entity's weight.
if (array_key_exists($entityName, $entitiesToBeProcessed)) {
foreach ($entitiesToBeProcessed[$entityName] as $dependentEntity) {
$entityWeights[$dependentEntity] = max((int) $entityWeights[$dependentEntity], (int) ($entityWeights[$entityName] + 1));
}
}
$sorter->add($entityName, $references);
}
// Numerically sort the weights now that we have them set
asort($entityWeights);
return $entityWeights;
// Return the list of entities ordered by weight
return $sorter->sort();
}

}
52 changes: 41 additions & 11 deletions ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction {
* Arguments present when loading the form
* @var array
*/
protected $args;
protected $args = [];

protected $_afform;

Expand Down Expand Up @@ -72,9 +72,9 @@ abstract protected function processForm();
*/
protected static function getJoinWhereClause($mainEntityName, $joinEntityName, $mainEntityId) {
$params = [];
if (self::fieldExists($joinEntityName, 'entity_id')) {
if (self::getEntityField($joinEntityName, 'entity_id')) {
$params[] = ['entity_id', '=', $mainEntityId];
if (self::fieldExists($joinEntityName, 'entity_table')) {
if (self::getEntityField($joinEntityName, 'entity_table')) {
$params[] = ['entity_table', '=', 'civicrm_' . \CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($mainEntityName)];
}
}
Expand All @@ -86,24 +86,54 @@ protected static function getJoinWhereClause($mainEntityName, $joinEntityName, $
}

/**
* Check if a field exists for a given entity
* Get field definition for a given entity
*
* @param $entityName
* @param $fieldName
* @return bool
* @return array|null
* @throws \API_Exception
*/
public static function fieldExists($entityName, $fieldName) {
if (empty(\Civi::$statics[__CLASS__][__FUNCTION__][$entityName])) {
public static function getEntityField($entityName, $fieldName) {
if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__][$entityName])) {
$fields = civicrm_api4($entityName, 'getFields', [
'checkPermissions' => FALSE,
'action' => 'create',
'select' => ['name'],
'includeCustom' => FALSE,
]);
\Civi::$statics[__CLASS__][__FUNCTION__][$entityName] = $fields->column('name');
\Civi::$statics[__CLASS__][__FUNCTION__][$entityName] = $fields->indexBy('name');
}
return in_array($fieldName, \Civi::$statics[__CLASS__][__FUNCTION__][$entityName]);
return \Civi::$statics[__CLASS__][__FUNCTION__][$entityName][$fieldName] ?? NULL;
}

/**
* @return array
*/
public function getArgs():array {
return $this->args;
}

/**
* @param array $args
* @return $this
*/
public function setArgs(array $args) {
$this->args = $args;
return $this;
}

/**
* @return string
*/
public function getName():string {
return $this->name;
}

/**
* @param string $name
* @return $this
*/
public function setName(string $name) {
$this->name = $name;
return $this;
}

}
2 changes: 1 addition & 1 deletion ext/afform/core/Civi/Api4/Action/Afform/Prefill.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private function loadEntity($entity, $id) {
'where' => self::getJoinWhereClause($entity['type'], $joinEntity, $item['id']),
'limit' => !empty($join['af-repeat']) ? $join['max'] ?? 0 : 1,
'select' => array_keys($join['fields']),
'orderBy' => self::fieldExists($joinEntity, 'is_primary') ? ['is_primary' => 'DESC'] : [],
'orderBy' => self::getEntityField($joinEntity, 'is_primary') ? ['is_primary' => 'DESC'] : [],
]);
}
$this->_data[$entity['name']][] = $data;
Expand Down
Loading

0 comments on commit 5dedaa3

Please sign in to comment.