<?php
namespace OpenObjects\Bundle\CoreBundle\Service;
use OpenObjects\Bundle\StyleGuideBundle\Form\FieldType\FieldsetComponentType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormError;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Validator\ConstraintViolation;
/**
* Class FormErrorsSerializer
* @package OpenObjects\Bundle\CoreBundle\Service
*/
class FormErrorsSerializer
{
/**
* @var bool
*/
private $breakErrorLoop = false;
/**
* @var array
*/
private $originalFormErrors = [];
/**
* @param Form $form
* @param bool $flatArray
* @param bool $addFormName
* @param string $glueKeys
*
* @return array
*/
public function serializeFormErrors(Form $form, $flatArray = false, $addFormName = false, $glueKeys = '_')
{
$errors = [];
$errors['global'] = [];
$errors['fields'] = [];
foreach ($form->getErrors() as $error) {
$errors['global'][] = $error->getMessage();
}
$errors['fields'] = $this->serialize($form);
if ($flatArray) {
$errors['fields'] = $this->arrayFlatten(
$errors['fields'],
$glueKeys,
(($addFormName) ? $form->getName() : '')
);
}
return $errors;
}
/**
* @param Form $form
*
* @return array
*/
private function serialize(Form $form) : array
{
$localErrors = [];
if ($form->getErrors()->count() > 0) {
foreach ($form->getErrors() as $error) {
$localErrors = $this->buildErrorArray($error);
}
} else {
foreach ($form->getIterator() as $key => $child) {
foreach ($child->getErrors() as $error) {
if (!empty($this->serialize($child))) {
$localErrors[$key] = $this->buildErrorArray($error);
}
}
if (count($child->getIterator()) > 0 && ($child instanceof Form)) {
if (!empty($this->serialize($child))) {
$localErrors[$key] = $this->serialize($child);
}
}
}
}
return $localErrors;
}
/**
* @param Form $form
* @param array $errors
*/
public function unserialize(Form &$form, array $errors)
{
foreach ($errors as $field => $error) {
if ($this->breakErrorLoop) {
break;
}
if (count($errors) > 0 && !isset($error['message'])) {
if ($this->isFieldset($form, $field) && !empty($error)) {
$form->get($field)->addError(
new FormError(
ucfirst(str_replace('_', ' ', $field)) . ' is invalid.'
)
);
}
$this->unserialize($form, $error);
} else {
$this->searchForm($form, $field, $error);
}
}
}
/**
* @param Form $form
* @param $field
*
* @return bool
*/
private function isFieldset(Form $form, $field) : bool
{
if ($form->has($field)) {
return $form->get($field)
->getConfig()
->getType()
->getInnerType() instanceof FieldsetComponentType;
}
return false;
}
/**
* @param Form $form
* @param $needle
* @param $error
*
* @return void
*/
private function searchForm(Form &$form, string $needle, array $error)
{
$searchKey = $form->getName();
// If this is the root form then set the search key as the needle.
if (!$form->getParent()) {
$searchKey = $needle;
}
if ($form->has($needle) && $this->keyExists($this->originalFormErrors['fields'], $searchKey)) {
$addError = true;
foreach ($form->get($needle)->getErrors() as $item) {
if ($item->getMessage() == $error['message']) {
$addError = false;
}
}
if ($addError) {
$formError = $this->createNewFormError($form, $error, $needle);
$form->get($needle)->addError($formError);
}
} else {
$originalFormErrors = $this->originalFormErrors;
foreach ($form->getIterator() as $key => $child) {
if (isset($this->originalFormErrors['fields'][$key])) {
$originalFormErrors = $this->originalFormErrors['fields'][$key];
}
if (!$child instanceof Form) {
continue;
}
$childIterator = $child->getIterator();
$numItems = $childIterator->count();
$counter = 0;
foreach ($childIterator as $name => $field) {
if ($this->isTimestamp($name)) {
foreach ($originalFormErrors as $fieldName => $fieldValue) {
// PHP datetime needs to be divided by 1000 to match the unix time indexed field.
$epochName = str_replace('.', '', ($fieldName/1000));
if ($name == $epochName) {
if ($child->get($name)->getErrors()->count() === 0) {
$formError = $this->createNewFormError($field, $error, $needle);
if ($child->get($name)->has($needle)) {
$child->get($name)->get($needle)->addError($formError);
}
}
}
}
if (++$counter === $numItems) {
$this->breakErrorLoop = true;
break 2;
}
}
}
if ($form->get($key)->has($needle) && isset($originalFormErrors[$key])) {
$formError = $this->createNewFormError($child, $error, $needle);
$form->get($key)->get($needle)->addError($formError);
// Remove from original errors array.
unset($this->originalFormErrors['fields'][$key][$needle]);
break;
}
if (count($child->getIterator()) > 0 && ($child instanceof Form)) {
$this->searchForm($child, $needle, $error);
}
}
}
}
/**
* @param $timestamp
*
* @return bool
*/
private function isTimestamp($timestamp) : bool
{
$check = (is_int($timestamp) OR is_float($timestamp))
? $timestamp
: (string) (int) $timestamp;
return ($check === $timestamp)
&& ( (int) $timestamp <= PHP_INT_MAX)
&& ( (int) $timestamp >= ~PHP_INT_MAX)
&& $check != 0;
}
/**
* @param $errorIn
*
* @return array
*/
private function buildErrorArray(FormError $errorIn) : array
{
$errorOut = [];
$errorOut['message'] = $errorIn->getMessage();
$errorOut['messageTemplate'] = $errorIn->getMessageTemplate();
$errorOut['messagePluralization'] = $errorIn->getMessagePluralization();
$errorOut['messageParameters'] = $errorIn->getMessageParameters();
if ($errorCause = $errorIn->getCause()) {
$errorOut['cause']['message'] = $errorCause->getMessage();
$errorOut['cause']['messageTemplate'] = $errorCause->getMessageTemplate();
$errorOut['cause']['parameters'] = $errorCause->getParameters();
$errorOut['cause']['plural'] = $errorCause->getPlural();
$errorOut['cause']['propertyPath'] = $errorCause->getPropertyPath();
$errorOut['cause']['invalidValue'] = $errorCause->getInvalidValue();
$errorOut['cause']['constraint'] = serialize($errorCause->getConstraint());
$errorOut['cause']['code'] = $errorCause->getCode();
$errorOut['cause']['cause'] = $errorCause->getCause();
}
return $errorOut;
}
/**
* @param Form $form
* @param $error
* @param $field
*
* @return FormError
*/
private function createNewFormError(Form $form, $error, $field) : FormError
{
if (isset($error['cause']['cause'])) {
$error['cause']['propertyPath'] =
$error['cause']['cause']['propertyPath'] ?? 'children[' . $field . '].data';
$error['cause']['invalidValue'] = $error['cause']['cause']['invalidValue'] ?? null;
$error['cause']['plural'] = $error['cause']['cause']['plural'] ?? null;
$error['cause']['code'] = $error['cause']['cause']['code'] ?? null;
$error['cause']['constraint'] = $error['cause']['cause']['constraint'] ?? null;
}
$constraint = null;
if (isset($error['cause']['constraint'])) {
$constraint = unserialize($error['cause']['constraint']);
}
return new FormError(
$error['message'],
$error['messageTemplate'] ?? null,
$error['messageParameters'] ?? [],
$error['messagePluralization'] ?? null,
new ConstraintViolation(
$error['cause']['message'],
$error['cause']['messageTemplate'],
$error['cause']['parameters'],
$form,
$error['cause']['propertyPath'] ?? $field . '.data',
$error['cause']['invalidValue'] ?? '',
$error['cause']['plural'] ?? null,
$error['cause']['code'] ?? null,
$constraint,
$error['cause']['cause'] ?? null
)
);
}
/**
* @param $array
* @param string $separator
* @param string $flattenedKey
*
* @return array
*/
private function arrayFlatten(array $array, string $separator = '_', string $flattenedKey = '') : array
{
$flattenedArray = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$flattenedArray = array_merge(
$flattenedArray,
$this->arrayFlatten(
$value,
$separator,
(
strlen($flattenedKey) > 0 ? $flattenedKey . $separator : ""
) . $key
)
);
} else {
$flattenedArray[(strlen($flattenedKey) > 0 ? $flattenedKey . $separator : "") . $key] = $value;
}
}
return $flattenedArray;
}
/**
* @param $array
* @param $keySearch
*
* @return bool
*/
protected function keyExists($array, $keySearch)
{
foreach ($array as $key => $item) {
if (
$key == $keySearch ||
(is_array($item) && $this->keyExists($item, $keySearch))
) {
return true;
}
}
return false;
}
/**
* @param array $errors
*
* @return FormErrorsSerializer
*/
public function setOriginalFormErrors(array $errors) : FormErrorsSerializer
{
$this->originalFormErrors = $errors;
return $this;
}
/**
* @return array
*/
public function getOriginalFormErrors() : array
{
return $this->originalFormErrors;
}
}