Source of file PayloadManifestParser.php
Size: 8,492 Bytes - Last Modified: 2021-12-23T10:01:46+00:00
/var/www/docs.ssmods.com/process/src/code/PayloadManifestParser.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | <?php /** * Class PayloadManifestParser */ class PayloadManifestParser { const TYPE_FIELD = 'field'; const TYPE_METHOD = 'method'; const TYPE_RELATION = 'relation'; /** * @var string */ private static $key_transformer = 'LowerCamelCaseKeyTransformer'; /** * @var KeyTransformer */ private $keyTransformer; /** * @var array */ private $validationErrors; public function __construct() { $this->keyTransformer = Config::inst()->get(PayloadManifestParser::class, 'key_transformer'); $this->validationErrors = []; } /** * Obtains the read payload manifest for the given class. * * @param $class * * @return array|scalar */ public function getManifest($class) { return Config::inst()->get($class, 'read_payload'); } /** * @return array */ public function getValidationErrors() { return $this->validationErrors; } /** * Resets errors array */ public function clearValidationErrors() { $this->validationErrors = []; } /** * Lists a validation error if not present yet. * * @param $error */ private function validationError($error) { if (!in_array($error, $this->validationErrors)) { $this->validationErrors[] = $error; } } /** * Generates a human-readable error message if a value is missing. * * @param $record * @param $field */ private function required($record, $field) { $this->validationError(_t('PayloadManifestParser.ERR_REQUIRED', '"{field}" fehlt für {class} "{title}"', '', [ 'field' => $record->fieldLabel($field), 'class' => _t($record->class . '.SINGULARNAME'), 'title' => $record->getTitle() ])); } /** * Generates a human-readable error message if entries in a relation list are missing. * * @param $record * @param $field */ private function missing($record, $field) { $this->validationError(_t('PayloadManifestParser.ERR_MISSING', 'Keine {field}-Einträge für {class} "{title}" gefunden', '', [ 'field' => _t($record->$field()->ClassName . '.PLURALNAME'), 'class' => _t($record->class . '.SINGULARNAME'), 'title' => $record->getTitle() ])); } /** * Checks if payload can be commited. * NOTE: commit() has be to executed first. * * @return bool */ public function canCommit() { return count($this->validationErrors) < 1; } /** * Applies the transformer rules to parsed payload. * * @param $payload * * @return array */ private function transform($payload) { // Transform keys $preparedPayload = []; array_walk($payload, function ($field, $key, $transformer) use (&$preparedPayload) { if (is_int($key)) { $key = $field; } if (is_array($key)) { $key = array_keys($key)[0]; } $preparedPayload[$transformer::transform($key)] = $field; }, $this->keyTransformer); return $preparedPayload; } /** * Parses one entry of the read payload manifest. * This is the main algorithm implementation. * * @param $record DataObject * @param $key int|string * @param $value string|bool|array * @param $collectErrors bool * * @return array */ private function parseEntry($record, $key, $value, $collectErrors = true) { /** * 1. Normalize NVP, e.g. 'ID' --> 'ID' => true */ if (is_int($key)) { $key = $value; $value = true; } /** * 2. Determine required and payload value * - if: Check if value is bool --> Field, Method or Relation * - elseif 1: Check if value is string --> Custom value mapping via method * - elseif 2: Check if value is array --> Extended config with value and required flag */ $required = false; $payload = null; $type = null; // Check if value is bool --> simple required check if (is_bool($value)) { $required = $value; /** * Check if key is: * - ID * - Relation (has_one) * - Relation (has_many, many_many, belongs_many_many) * - Method */ if ($record->hasField($key)) { $type = self::TYPE_FIELD; $payload = $record->$key; } elseif ($record->hasOneComponent($key)) { $type = self::TYPE_RELATION; if ($record->$key()->exists()) { $payload = $this->commit($record->$key(), $required); } } elseif ($record->hasManyComponent($key) || $record->manyManyComponent($key)) { $type = self::TYPE_RELATION; // Recursively collect relation record payload if ($record->$key()->exists()) { $payload = []; foreach ($record->$key() as $relationRecord) { $payload[] = $this->commit($relationRecord, $required); } } } elseif ($record->hasMethod($key)) { $type = self::TYPE_METHOD; $payload = $record->$key(); } } elseif (is_string($value)) { if (!$record->hasMethod($value)) { trigger_error("The class {$record->class} must define the method \"{$value}\"", E_USER_ERROR); } $type = self::TYPE_METHOD; $required = true; $methodPayload = $record->$value(); if ($methodPayload instanceof DataList) { $payload = []; foreach ($methodPayload as $payloadRecord) { $payload[] = $this->commit($payloadRecord); } } else { $payload = $methodPayload; } } elseif (is_array($value)) { if (!key_exists('required', $value) || !key_exists('mapping', $value)) { trigger_error("CQRS definition of \"$key\" needs to specify \"required\" (true/false) and \"mapping\" fields (record method name).", E_USER_ERROR); } if (!$record->hasMethod($value['mapping'])) { trigger_error("The class {$record->class} must define the method \"{$value['mapping']}\"", E_USER_ERROR); } $type = self::TYPE_METHOD; $required = $value['required']; $method = $value['mapping']; $methodPayload = $record->$method(); if ($methodPayload instanceof DataList) { $payload = []; foreach ($methodPayload as $payloadRecord) { $payload[] = $this->commit($payloadRecord); } } else { $payload = $methodPayload; } } // Remove empty values if (is_array($payload)) $payload = array_filter($payload); /** * 3. Do error reporting */ if ($required && empty($payload) && $collectErrors === true) { if ($type === self::TYPE_RELATION) { $this->missing($record, $key); } else { $this->required($record, $key); } } return [ $key, $payload ]; } /** * Generates commit-ready data. * * @param $record * @param bool $collectErrors * * @return array */ public function commit($record, $collectErrors = true) { $payload = []; $class = $record->class; // Clear previous validation errors $this->clearValidationErrors(); foreach ($this->getManifest($class) as $key => $value) { list($key, $data) = $this->parseEntry($record, $key, $value, $collectErrors); $payload[$key] = $data; } // Add validation errors to the affected record for later usage $record->cqrsValidationErrors = $this->validationErrors; return $this->transform($payload); } } |