2021-08-09 09:18:46 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace LDC\LDCCRM\Models;
|
|
|
|
|
|
|
|
|
|
use LDC\LDCCRM\Enums\ArcadiaOrderType;
|
|
|
|
|
use LDC\LDCCRM\Enums\Country;
|
2021-12-19 13:43:26 +01:00
|
|
|
use LDC\LDCCRM\Enums\LogLevel;
|
2021-10-18 16:05:03 +02:00
|
|
|
use LDC\LDCCRM\Enums\QuestionType;
|
2021-12-19 13:43:26 +01:00
|
|
|
use LDC\LDCCRM\Exceptions\AccessError;
|
2021-08-09 09:18:46 +02:00
|
|
|
use LDC\LDCCRM\Exceptions\ValidationException;
|
|
|
|
|
use LDC\LDCCRM\Transport\ApiInterface;
|
|
|
|
|
use LDC\LDCCRM\Utils;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ignore
|
|
|
|
|
* Base Class for crm client models
|
|
|
|
|
*
|
|
|
|
|
* @category Api Models
|
|
|
|
|
* @package LDCCrmClient
|
|
|
|
|
* @subpackage models
|
2021-12-19 13:43:26 +01:00
|
|
|
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
abstract class ClientModel implements \JsonSerializable
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Is true if this model was not filled from crm
|
|
|
|
|
*
|
|
|
|
|
* @var boolean
|
|
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected bool $isNew = true;
|
2021-08-09 09:18:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The Api object that will handle requests and responses
|
|
|
|
|
*
|
|
|
|
|
* @var \LDC\LDCCRM\Transport\ApiInterface
|
|
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected static ApiInterface $apiInterface;
|
2021-08-09 09:18:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validation rules
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected static array $validation = [];
|
2021-08-09 09:18:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Model fields
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected array $modelFields = [];
|
2021-08-09 09:18:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The crm fields
|
|
|
|
|
* that should not be visible
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected array $invisibleFields = [];
|
2021-08-09 09:18:46 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Model updated values
|
2021-12-19 13:43:26 +01:00
|
|
|
*
|
|
|
|
|
* @var array
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-09-15 12:11:28 +02:00
|
|
|
protected array $updatedValues = [];
|
2021-08-09 09:18:46 +02:00
|
|
|
|
2021-12-19 13:43:26 +01:00
|
|
|
/**
|
|
|
|
|
* CrmClientModel
|
|
|
|
|
*/
|
2021-08-09 09:18:46 +02:00
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
self::setUpApiInterface();
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-19 13:43:26 +01:00
|
|
|
/**
|
|
|
|
|
* Generic accessor
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @return mixed|null Returns null if the property was not found any way.
|
|
|
|
|
*/
|
2021-08-09 09:18:46 +02:00
|
|
|
public function __get(string $field)
|
|
|
|
|
{
|
|
|
|
|
$props = \array_diff(
|
|
|
|
|
$this->modelFields,
|
|
|
|
|
$this->invisibleFields
|
|
|
|
|
);
|
|
|
|
|
if (in_array($field, $props)) {
|
2021-12-19 13:43:26 +01:00
|
|
|
return Utils::getRawProperty($this, $field);
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-12-19 13:43:26 +01:00
|
|
|
* Set prop on the model.
|
2021-08-09 09:18:46 +02:00
|
|
|
*
|
|
|
|
|
* @param string $field
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param mixed $value
|
|
|
|
|
* @return void
|
|
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
|
|
|
|
|
* @throws \LDC\LDCCRM\Exceptions\AccessError If trying to set a value on a unknown property.
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
public function __set(string $field, #[\SensitiveParameter] mixed $value): void
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
2021-12-19 13:43:26 +01:00
|
|
|
try {
|
|
|
|
|
if (in_array($field, $this->modelFields)) {
|
|
|
|
|
$type = $this->getPropType($field);
|
|
|
|
|
if (!is_null($value) and !$this->checkFieldType($type, $value)) {
|
|
|
|
|
throw new ValidationException("Wrong type for $field");
|
|
|
|
|
}
|
|
|
|
|
$this->validateValue($field, $value);
|
|
|
|
|
$this->updatedValues[$field] = $value;
|
|
|
|
|
Utils::setRawProperty($this, $field, $value);
|
|
|
|
|
return;
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
|
|
|
|
} catch (\ReflectionException $e) {
|
|
|
|
|
// Do nothing.
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
throw new AccessError(\get_class($this), $field);
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-19 13:43:26 +01:00
|
|
|
/**
|
|
|
|
|
* Create the Api interface
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2021-08-09 09:18:46 +02:00
|
|
|
protected static function setUpApiInterface()
|
|
|
|
|
{
|
2023-10-05 12:16:43 +02:00
|
|
|
self::$apiInterface = self::$apiInterface ?? new ApiInterface();
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-12-19 13:43:26 +01:00
|
|
|
* Fill values into model using validation rules.
|
2021-08-09 09:18:46 +02:00
|
|
|
*
|
|
|
|
|
* @param array $properties
|
|
|
|
|
* @return void
|
2021-12-19 13:43:26 +01:00
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
public function fill(#[\SensitiveParameter] array $properties)
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
|
|
|
|
if (count($properties)) {
|
|
|
|
|
foreach ($properties as $field => $value) {
|
2021-12-19 13:43:26 +01:00
|
|
|
try {
|
|
|
|
|
$type = $this->getPropType($field);
|
|
|
|
|
// * Force boolean
|
|
|
|
|
if ($type === 'bool') {
|
|
|
|
|
$value = (\is_int($value) and ($value === 1 or $value === 0)) ? (bool)$value : $value;
|
|
|
|
|
}
|
|
|
|
|
if (!is_null($value) and !$this->checkFieldType($type, $value)) {
|
|
|
|
|
throw new ValidationException("Wrong type for $field");
|
|
|
|
|
}
|
|
|
|
|
$this->validateValue($field, $value);
|
|
|
|
|
$this->updatedValues[$field] = $value;
|
|
|
|
|
Utils::setRawProperty($this, $field, $value);
|
2023-10-05 12:16:43 +02:00
|
|
|
} catch (AccessError | \ReflectionException) {
|
2021-12-19 13:43:26 +01:00
|
|
|
Utils::log(\sprintf(
|
|
|
|
|
'%s::fill : unknown property access for %s, it does not exist on object.',
|
|
|
|
|
\get_class($this),
|
|
|
|
|
$field
|
2023-09-05 16:40:45 +02:00
|
|
|
), LogLevel::DEBUG);
|
2021-12-19 13:43:26 +01:00
|
|
|
continue;
|
2021-10-18 16:05:03 +02:00
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
} //end foreach
|
2023-10-05 12:16:43 +02:00
|
|
|
} //end if
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set all properties to without any validation
|
|
|
|
|
*
|
|
|
|
|
* @param array $properties
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
protected function fillRaw(#[\SensitiveParameter] array $properties)
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
|
|
|
|
if (count($properties)) {
|
|
|
|
|
foreach ($properties as $field => $value) {
|
2021-12-19 13:43:26 +01:00
|
|
|
try {
|
|
|
|
|
Utils::setRawProperty($this, $field, $value);
|
|
|
|
|
} catch (AccessError $e) {
|
|
|
|
|
Utils::log(\sprintf(
|
|
|
|
|
'%s::fillRaw : unknown property access for %s, it does not exist on object.',
|
|
|
|
|
\get_class($this),
|
|
|
|
|
$field
|
2023-09-05 16:40:45 +02:00
|
|
|
), LogLevel::DEBUG);
|
2021-12-19 13:43:26 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate all model properties
|
|
|
|
|
*
|
2021-12-19 13:43:26 +01:00
|
|
|
* * All properties are nullable
|
|
|
|
|
*
|
|
|
|
|
* @param array $ignore
|
2021-08-09 09:18:46 +02:00
|
|
|
* @return array the validated values
|
2021-12-19 13:43:26 +01:00
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
|
|
|
|
protected function validateAllValues(array $ignore = []): array
|
|
|
|
|
{
|
|
|
|
|
$out = [];
|
|
|
|
|
foreach (static::$validation as $field => $validationRules) {
|
2021-12-19 13:43:26 +01:00
|
|
|
if (\in_array($field, $ignore) or \is_null(Utils::getRawProperty($this, $field))) {
|
2021-08-09 09:18:46 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
$this->validateValue($field, Utils::getRawProperty($this, $field));
|
|
|
|
|
$out[$field] = Utils::getRawProperty($this, $field);
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
return $out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate a value against its rules
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param mixed $value
|
2021-08-09 09:18:46 +02:00
|
|
|
* @return boolean
|
2021-12-19 13:43:26 +01:00
|
|
|
* @throws \ErrorException If a field has no validation rule.
|
|
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
|
|
|
|
|
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
protected static function validateValue(string $field, #[\SensitiveParameter] mixed $value): bool
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
|
|
|
|
if (!isset(static::$validation[$field])) {
|
2021-12-19 13:43:26 +01:00
|
|
|
throw new \ErrorException("$field has no validation rule");
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
$validator = Utils::makeValidator(
|
|
|
|
|
[$field => $value],
|
|
|
|
|
[$field => static::$validation[$field]]
|
|
|
|
|
);
|
2021-12-19 13:43:26 +01:00
|
|
|
/** @throws \LDC\LDCCRM\Exceptions\ValidationException */
|
|
|
|
|
$validator();
|
2021-08-09 09:18:46 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check a value against its field type
|
|
|
|
|
*
|
|
|
|
|
* @param string $type
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param mixed $value
|
|
|
|
|
* @return boolean Return true if $type matches $value type.
|
|
|
|
|
* @throws \ErrorException If type is not handled,
|
|
|
|
|
* handled types are boolean, string, integer, \LDC\LDCCRM\Enums\Country, \LDC\LDCCRM\Enums\ArcadiaOrderType .
|
|
|
|
|
* @phpcs:ignore Generic.Files.LineLength.TooLong
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
2023-10-05 12:16:43 +02:00
|
|
|
protected function checkFieldType(string $type, #[\SensitiveParameter] mixed $value): bool
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
2021-12-19 13:43:26 +01:00
|
|
|
/**
|
|
|
|
|
* Checks if the value is valid for a specific enum class
|
2023-10-05 12:16:43 +02:00
|
|
|
*
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param string $className
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return boolean
|
|
|
|
|
*/
|
|
|
|
|
$checkValueIsEnum = function (string $className, $value) {
|
|
|
|
|
try {
|
|
|
|
|
$value = \is_int($value) ? $className::make($value) : $value;
|
|
|
|
|
} finally {
|
|
|
|
|
return \is_object($value) and (get_class($value) === $className or
|
|
|
|
|
\in_array($className, \class_parents($value)));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-08-09 09:18:46 +02:00
|
|
|
switch ($type) {
|
|
|
|
|
case '\\' . Country::class:
|
2021-10-18 16:05:03 +02:00
|
|
|
case '\\' . QuestionType::class:
|
2021-08-09 09:18:46 +02:00
|
|
|
case '\\' . ArcadiaOrderType::class:
|
2021-12-19 13:43:26 +01:00
|
|
|
return $checkValueIsEnum(\ltrim($type, '\\'), $value);
|
|
|
|
|
case 'boolean':
|
|
|
|
|
return \is_bool(\is_int($value) ? (bool)$value : $value);
|
2021-10-18 16:05:03 +02:00
|
|
|
case 'array':
|
|
|
|
|
return \is_array($value);
|
2021-08-09 09:18:46 +02:00
|
|
|
case 'string':
|
|
|
|
|
return \is_string($value);
|
2021-12-19 13:43:26 +01:00
|
|
|
case 'integer':
|
2021-08-09 09:18:46 +02:00
|
|
|
return \is_int($value);
|
|
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
throw new \ErrorException(\sprintf("Unhandled $type for %s", \json_encode($value)));
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the type of any of this class property
|
|
|
|
|
* from doc comment
|
|
|
|
|
*
|
|
|
|
|
* @param string $property
|
|
|
|
|
* @return string|null the type or null if not defined
|
2021-12-19 13:43:26 +01:00
|
|
|
* @throws \ErrorException If the property has no comment to read.
|
|
|
|
|
* @throws \ErrorException If a type for a property could ne be recognized.
|
|
|
|
|
* @throws \ReflectionException If the class or property does not exist.
|
|
|
|
|
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
|
|
|
|
protected function getPropType(string $property)
|
|
|
|
|
{
|
2021-12-06 00:16:13 +01:00
|
|
|
$matches = [];
|
2021-12-19 13:43:26 +01:00
|
|
|
$propReflection = new \ReflectionProperty(static::class, $property);
|
2021-12-06 00:16:13 +01:00
|
|
|
$docComment = $propReflection->getDocComment();
|
2021-12-19 13:43:26 +01:00
|
|
|
if (!$docComment) {
|
|
|
|
|
throw new \ErrorException(\sprintf("%s $property has not comment to parse.", \get_class($this)));
|
|
|
|
|
}
|
2021-08-09 09:18:46 +02:00
|
|
|
preg_match_all('/@var ([\w_\x5c]+)/', $docComment, $matches);
|
2021-12-19 13:43:26 +01:00
|
|
|
if (
|
|
|
|
|
isset($matches[1][0]) and \in_array($matches[1][0], [
|
|
|
|
|
'string', 'integer', 'boolean', 'array', '\\' . Country::class, '\\' . ArcadiaOrderType::class,
|
2023-10-05 12:16:43 +02:00
|
|
|
'\\' . QuestionType::class
|
2021-12-19 13:43:26 +01:00
|
|
|
], true)
|
|
|
|
|
) {
|
2021-08-09 09:18:46 +02:00
|
|
|
return $matches[1][0];
|
|
|
|
|
}
|
2021-12-19 13:43:26 +01:00
|
|
|
throw new \ErrorException("Unknown type for property $property");
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|
2021-08-11 17:26:30 +02:00
|
|
|
|
|
|
|
|
/**
|
2021-12-19 13:43:26 +01:00
|
|
|
* Assert primaryKey is valid.
|
2021-08-11 17:26:30 +02:00
|
|
|
*
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param mixed $primaryKey
|
2021-08-11 17:26:30 +02:00
|
|
|
* @return void
|
2023-10-05 12:16:43 +02:00
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a primary key is not a valid integer.
|
2021-08-11 17:26:30 +02:00
|
|
|
*/
|
2023-09-05 16:40:45 +02:00
|
|
|
protected static function assertValidPrimaryKey(mixed $primaryKey): void
|
2021-08-11 17:26:30 +02:00
|
|
|
{
|
|
|
|
|
$validator = Utils::makeValidator(
|
|
|
|
|
['id' => $primaryKey],
|
|
|
|
|
['id' => 'required|int']
|
|
|
|
|
);
|
2021-08-13 10:24:57 +02:00
|
|
|
if ($validator()) {
|
2021-08-11 17:26:30 +02:00
|
|
|
throw new ValidationException(implode(' ', $validator->errors()->get('id')));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-12-19 13:43:26 +01:00
|
|
|
* Assert hashcode is valid.
|
2021-08-11 17:26:30 +02:00
|
|
|
*
|
2021-12-19 13:43:26 +01:00
|
|
|
* @param mixed $hashcode
|
2021-08-11 17:26:30 +02:00
|
|
|
* @return void
|
2023-10-05 12:16:43 +02:00
|
|
|
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a hashcode is not a valid UUID.
|
2021-08-11 17:26:30 +02:00
|
|
|
*/
|
2023-09-05 16:40:45 +02:00
|
|
|
protected static function assertValidHascode(mixed $hashcode): void
|
2021-08-11 17:26:30 +02:00
|
|
|
{
|
|
|
|
|
$validator = Utils::makeValidator(
|
|
|
|
|
['hashcode' => $hashcode],
|
|
|
|
|
['hashcode' => 'required|uuid']
|
|
|
|
|
);
|
2021-08-13 10:24:57 +02:00
|
|
|
if ($validator()) {
|
2021-08-11 17:26:30 +02:00
|
|
|
throw new ValidationException(implode(' ', $validator->errors()->get('hashcode')));
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-09 09:18:46 +02:00
|
|
|
}
|