467 lines
12 KiB
PHP
467 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Extbase\Error;
|
|
|
|
/**
|
|
* Result object for operations dealing with objects, such as the Property Mapper or the Validators.
|
|
*/
|
|
class Result
|
|
{
|
|
/**
|
|
* @var Error[]
|
|
*/
|
|
protected $errors = [];
|
|
|
|
/**
|
|
* Caches the existence of errors
|
|
* @var bool
|
|
*/
|
|
protected $errorsExist = false;
|
|
|
|
/**
|
|
* @var Warning[]
|
|
*/
|
|
protected $warnings = [];
|
|
|
|
/**
|
|
* Caches the existence of warning
|
|
* @var bool
|
|
*/
|
|
protected $warningsExist = false;
|
|
|
|
/**
|
|
* @var Notice[]
|
|
*/
|
|
protected $notices = [];
|
|
|
|
/**
|
|
* Caches the existence of notices
|
|
* @var bool
|
|
*/
|
|
protected $noticesExist = false;
|
|
|
|
/**
|
|
* The result objects for the sub properties
|
|
*
|
|
* @var Result[]
|
|
*/
|
|
protected $propertyResults = [];
|
|
|
|
/**
|
|
* @var Result
|
|
*/
|
|
protected $parent;
|
|
|
|
/**
|
|
* Injects the parent result and propagates the
|
|
* cached error states upwards
|
|
*/
|
|
public function setParent(Result $parent): void
|
|
{
|
|
if ($this->parent !== $parent) {
|
|
$this->parent = $parent;
|
|
if ($this->hasErrors()) {
|
|
$parent->setErrorsExist();
|
|
}
|
|
if ($this->hasWarnings()) {
|
|
$parent->setWarningsExist();
|
|
}
|
|
if ($this->hasNotices()) {
|
|
$parent->setNoticesExist();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an error to the current Result object
|
|
*/
|
|
public function addError(Error $error): void
|
|
{
|
|
$this->errors[] = $error;
|
|
$this->setErrorsExist();
|
|
}
|
|
|
|
/**
|
|
* Add a warning to the current Result object
|
|
*/
|
|
public function addWarning(Warning $warning): void
|
|
{
|
|
$this->warnings[] = $warning;
|
|
$this->setWarningsExist();
|
|
}
|
|
|
|
/**
|
|
* Add a notice to the current Result object
|
|
*/
|
|
public function addNotice(Notice $notice): void
|
|
{
|
|
$this->notices[] = $notice;
|
|
$this->setNoticesExist();
|
|
}
|
|
|
|
/**
|
|
* Get all errors in the current Result object (non-recursive)
|
|
*
|
|
* @return Error[]
|
|
*/
|
|
public function getErrors(): array
|
|
{
|
|
return $this->errors;
|
|
}
|
|
|
|
/**
|
|
* Get all warnings in the current Result object (non-recursive)
|
|
*
|
|
* @return Warning[]
|
|
*/
|
|
public function getWarnings(): array
|
|
{
|
|
return $this->warnings;
|
|
}
|
|
|
|
/**
|
|
* Get all notices in the current Result object (non-recursive)
|
|
*
|
|
* @return Notice[]
|
|
*/
|
|
public function getNotices(): array
|
|
{
|
|
return $this->notices;
|
|
}
|
|
|
|
/**
|
|
* Get the first error object of the current Result object (non-recursive)
|
|
*
|
|
* @return bool|Error
|
|
*/
|
|
public function getFirstError()
|
|
{
|
|
reset($this->errors);
|
|
return current($this->errors);
|
|
}
|
|
|
|
/**
|
|
* Get the first warning object of the current Result object (non-recursive)
|
|
*
|
|
* @return bool|Warning
|
|
*/
|
|
public function getFirstWarning()
|
|
{
|
|
reset($this->warnings);
|
|
return current($this->warnings);
|
|
}
|
|
|
|
/**
|
|
* Get the first notice object of the current Result object (non-recursive)
|
|
*
|
|
* @return bool|Notice
|
|
*/
|
|
public function getFirstNotice()
|
|
{
|
|
reset($this->notices);
|
|
return current($this->notices);
|
|
}
|
|
|
|
/**
|
|
* Return a Result object for the given property path. This is
|
|
* a fluent interface, so you will probably use it like:
|
|
* $result->forProperty('foo.bar')->getErrors() -- to get all errors
|
|
* for property "foo.bar"
|
|
*/
|
|
public function forProperty(?string $propertyPath): Result
|
|
{
|
|
if ($propertyPath === '' || $propertyPath === null) {
|
|
return $this;
|
|
}
|
|
if (str_contains($propertyPath, '.')) {
|
|
return $this->recurseThroughResult(explode('.', $propertyPath));
|
|
}
|
|
if (!isset($this->propertyResults[$propertyPath])) {
|
|
$this->propertyResults[$propertyPath] = new self();
|
|
$this->propertyResults[$propertyPath]->setParent($this);
|
|
}
|
|
return $this->propertyResults[$propertyPath];
|
|
}
|
|
|
|
/**
|
|
* @todo: consider making this method protected as it will and should not be called from an outside scope
|
|
*
|
|
* @internal only to be used within Extbase, not part of TYPO3 Core API.
|
|
*/
|
|
public function recurseThroughResult(array $pathSegments): Result
|
|
{
|
|
if (count($pathSegments) === 0) {
|
|
return $this;
|
|
}
|
|
|
|
$propertyName = array_shift($pathSegments);
|
|
|
|
if (!isset($this->propertyResults[$propertyName])) {
|
|
$this->propertyResults[$propertyName] = new self();
|
|
$this->propertyResults[$propertyName]->setParent($this);
|
|
}
|
|
|
|
return $this->propertyResults[$propertyName]->recurseThroughResult($pathSegments);
|
|
}
|
|
|
|
/**
|
|
* Sets the error cache to TRUE and propagates the information
|
|
* upwards the Result-Object Tree
|
|
*/
|
|
protected function setErrorsExist(): void
|
|
{
|
|
$this->errorsExist = true;
|
|
if ($this->parent !== null) {
|
|
$this->parent->setErrorsExist();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the warning cache to TRUE and propagates the information
|
|
* upwards the Result-Object Tree
|
|
*/
|
|
protected function setWarningsExist(): void
|
|
{
|
|
$this->warningsExist = true;
|
|
if ($this->parent !== null) {
|
|
$this->parent->setWarningsExist();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the notices cache to TRUE and propagates the information
|
|
* upwards the Result-Object Tree
|
|
*/
|
|
protected function setNoticesExist(): void
|
|
{
|
|
$this->noticesExist = true;
|
|
if ($this->parent !== null) {
|
|
$this->parent->setNoticesExist();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Does the current Result object have Notices, Errors or Warnings? (Recursively)
|
|
*/
|
|
public function hasMessages(): bool
|
|
{
|
|
return $this->errorsExist || $this->noticesExist || $this->warningsExist;
|
|
}
|
|
|
|
/**
|
|
* Clears the result
|
|
*/
|
|
public function clear(): void
|
|
{
|
|
$this->errors = [];
|
|
$this->notices = [];
|
|
$this->warnings = [];
|
|
|
|
$this->warningsExist = false;
|
|
$this->noticesExist = false;
|
|
$this->errorsExist = false;
|
|
|
|
$this->propertyResults = [];
|
|
}
|
|
|
|
/**
|
|
* Does the current Result object have Errors? (Recursively)
|
|
*/
|
|
public function hasErrors(): bool
|
|
{
|
|
if (count($this->errors) > 0) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->propertyResults as $subResult) {
|
|
if ($subResult->hasErrors()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Does the current Result object have Warnings? (Recursively)
|
|
*/
|
|
public function hasWarnings(): bool
|
|
{
|
|
if (count($this->warnings) > 0) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->propertyResults as $subResult) {
|
|
if ($subResult->hasWarnings()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Does the current Result object have Notices? (Recursively)
|
|
*/
|
|
public function hasNotices(): bool
|
|
{
|
|
if (count($this->notices) > 0) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->propertyResults as $subResult) {
|
|
if ($subResult->hasNotices()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all Error objects recursively. The result is an array,
|
|
* where the key is the property path where the error occurred, and the
|
|
* value is a list of all errors (stored as array)
|
|
*
|
|
* @return array<string,array<Error>>
|
|
*/
|
|
public function getFlattenedErrors(): array
|
|
{
|
|
$result = [];
|
|
$this->flattenErrorTree($result, []);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all Warning objects recursively. The result is an array,
|
|
* where the key is the property path where the warning occurred, and the
|
|
* value is a list of all warnings (stored as array)
|
|
*
|
|
* @return array<string,array<Warning>>
|
|
*/
|
|
public function getFlattenedWarnings(): array
|
|
{
|
|
$result = [];
|
|
$this->flattenWarningsTree($result, []);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all Notice objects recursively. The result is an array,
|
|
* where the key is the property path where the notice occurred, and the
|
|
* value is a list of all notices (stored as array)
|
|
*
|
|
* @return array<string,array<Notice>>
|
|
*/
|
|
public function getFlattenedNotices(): array
|
|
{
|
|
$result = [];
|
|
$this->flattenNoticesTree($result, []);
|
|
return $result;
|
|
}
|
|
|
|
protected function flattenErrorTree(array &$result, array $level): void
|
|
{
|
|
if (count($this->errors) > 0) {
|
|
$result[implode('.', $level)] = $this->errors;
|
|
}
|
|
foreach ($this->propertyResults as $subPropertyName => $subResult) {
|
|
$level[] = $subPropertyName;
|
|
$subResult->flattenErrorTree($result, $level);
|
|
array_pop($level);
|
|
}
|
|
}
|
|
|
|
protected function flattenWarningsTree(array &$result, array $level): void
|
|
{
|
|
if (count($this->warnings) > 0) {
|
|
$result[implode('.', $level)] = $this->warnings;
|
|
}
|
|
foreach ($this->propertyResults as $subPropertyName => $subResult) {
|
|
$level[] = $subPropertyName;
|
|
$subResult->flattenWarningsTree($result, $level);
|
|
array_pop($level);
|
|
}
|
|
}
|
|
|
|
protected function flattenNoticesTree(array &$result, array $level): void
|
|
{
|
|
if (count($this->notices) > 0) {
|
|
$result[implode('.', $level)] = $this->notices;
|
|
}
|
|
foreach ($this->propertyResults as $subPropertyName => $subResult) {
|
|
$level[] = $subPropertyName;
|
|
$subResult->flattenNoticesTree($result, $level);
|
|
array_pop($level);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge the given Result object into this one.
|
|
*/
|
|
public function merge(Result $otherResult): void
|
|
{
|
|
if ($otherResult->errorsExist) {
|
|
$this->mergeProperty($otherResult, 'getErrors', 'addError');
|
|
}
|
|
if ($otherResult->warningsExist) {
|
|
$this->mergeProperty($otherResult, 'getWarnings', 'addWarning');
|
|
}
|
|
if ($otherResult->noticesExist) {
|
|
$this->mergeProperty($otherResult, 'getNotices', 'addNotice');
|
|
}
|
|
|
|
foreach ($otherResult->getSubResults() as $subPropertyName => $subResult) {
|
|
/** @var Result $subResult */
|
|
if (array_key_exists($subPropertyName, $this->propertyResults) && $this->propertyResults[$subPropertyName]->hasMessages()) {
|
|
$this->forProperty((string)$subPropertyName)->merge($subResult);
|
|
} else {
|
|
$this->propertyResults[$subPropertyName] = $subResult;
|
|
$subResult->setParent($this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge a single property from the other result object.
|
|
*/
|
|
protected function mergeProperty(Result $otherResult, string $getterName, string $adderName): void
|
|
{
|
|
$getter = [$otherResult, $getterName];
|
|
$adder = [$this, $adderName];
|
|
|
|
if (!is_callable($getter) || !is_callable($adder)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($getter() as $messageInOtherResult) {
|
|
$adder($messageInOtherResult);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of all sub Result objects available.
|
|
*
|
|
* @return Result[]
|
|
*/
|
|
public function getSubResults(): array
|
|
{
|
|
return $this->propertyResults;
|
|
}
|
|
}
|