2021-08-09 09:18:46 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace LDC\LDCCRM\Models;
|
|
|
|
|
|
|
|
|
|
use Exception;
|
2021-08-16 13:22:34 +02:00
|
|
|
use JsonSerializable;
|
2021-08-09 09:18:46 +02:00
|
|
|
use Kwaadpepper\Enum\BaseEnum;
|
|
|
|
|
use LDC\LDCCRM\Enums\ArcadiaOrderType;
|
|
|
|
|
use LDC\LDCCRM\Enums\Country;
|
2021-10-18 16:05:03 +02:00
|
|
|
use LDC\LDCCRM\Enums\QuestionType;
|
2021-08-09 09:18:46 +02:00
|
|
|
use LDC\LDCCRM\Exceptions\ValidationException;
|
|
|
|
|
use LDC\LDCCRM\Transport\ApiInterface;
|
|
|
|
|
use LDC\LDCCRM\Utils;
|
|
|
|
|
use ReflectionClass;
|
|
|
|
|
use ReflectionProperty;
|
|
|
|
|
use TypeError;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ignore
|
|
|
|
|
* Base Class for crm client models
|
|
|
|
|
*
|
|
|
|
|
* @category Api Models
|
|
|
|
|
* @package LDCCrmClient
|
|
|
|
|
* @subpackage models
|
|
|
|
|
*/
|
2021-08-16 13:22:34 +02:00
|
|
|
abstract class CrmClientModel implements JsonSerializable
|
2021-08-09 09:18:46 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Is true if this model was not filled from crm
|
|
|
|
|
*
|
|
|
|
|
* @var boolean
|
|
|
|
|
*/
|
|
|
|
|
private $isNew = true;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The Api object that will handle requests and responses
|
|
|
|
|
*
|
|
|
|
|
* @var \LDC\LDCCRM\Transport\ApiInterface
|
|
|
|
|
*/
|
|
|
|
|
protected static $apiInterface;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validation rules
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected static $validation = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Model fields
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $modelFields = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The crm fields
|
|
|
|
|
* that should not be visible
|
|
|
|
|
*
|
|
|
|
|
* @var array
|
|
|
|
|
*/
|
|
|
|
|
protected $invisibleFields = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Model updated values
|
|
|
|
|
*/
|
|
|
|
|
protected $updatedValues = [];
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
self::setUpApiInterface();
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function __get(string $field)
|
|
|
|
|
{
|
|
|
|
|
$props = \array_diff(
|
|
|
|
|
$this->modelFields,
|
|
|
|
|
$this->invisibleFields
|
|
|
|
|
);
|
|
|
|
|
if (in_array($field, $props)) {
|
|
|
|
|
return self::getRawProperty($this, $field);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set prop
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @throws TypeError if the field has an incorrect value
|
|
|
|
|
* @return mixed|null The input value or null is this prop does not exists
|
|
|
|
|
*/
|
|
|
|
|
public function __set(string $field, $value)
|
|
|
|
|
{
|
|
|
|
|
if (in_array($field, $this->modelFields)) {
|
|
|
|
|
$type = $this->getPropType($field);
|
|
|
|
|
if (!$this->checkFieldType($type, $value)) {
|
|
|
|
|
throw new TypeError("Wrong type for $field");
|
|
|
|
|
}
|
|
|
|
|
$this->validateValue($field, $value);
|
|
|
|
|
$this->updatedValues[$field] = $value;
|
|
|
|
|
return $this->setRawProperty($this, $field, $value);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static function setUpApiInterface()
|
|
|
|
|
{
|
|
|
|
|
self::$apiInterface = self::$apiInterface ?: new ApiInterface();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fill values into model
|
|
|
|
|
*
|
|
|
|
|
* @param array $properties
|
|
|
|
|
* @return void
|
2021-10-14 11:46:11 +02:00
|
|
|
* @throws TypeError if a field has an incorrect value
|
2021-08-09 09:18:46 +02:00
|
|
|
*/
|
|
|
|
|
public function fill(array $properties)
|
|
|
|
|
{
|
|
|
|
|
if (count($properties)) {
|
|
|
|
|
foreach ($properties as $field => $value) {
|
2021-10-18 16:05:03 +02:00
|
|
|
$type = $this->getPropType($field);
|
2021-10-26 10:26:20 +02:00
|
|
|
// Force boolean
|
|
|
|
|
if ($type === 'bool') {
|
2021-10-26 12:36:10 +02:00
|
|
|
$value = (\is_int($value) and ($value === 1 or $value === 0)) ? (bool)$value : $value;
|
2021-10-26 10:26:20 +02:00
|
|
|
}
|
2021-10-18 16:05:03 +02:00
|
|
|
if (!$this->checkFieldType($type, $value)) {
|
|
|
|
|
throw new TypeError("Wrong type for $field");
|
|
|
|
|
}
|
2021-10-14 11:46:11 +02:00
|
|
|
$this->validateValue($field, $value);
|
|
|
|
|
$this->updatedValues[$field] = $value;
|
2021-08-09 09:18:46 +02:00
|
|
|
self::setRawProperty($this, $field, $value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set all properties to without any validation
|
|
|
|
|
*
|
|
|
|
|
* @param array $properties
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
protected function fillRaw(array $properties)
|
|
|
|
|
{
|
|
|
|
|
if (count($properties)) {
|
|
|
|
|
foreach ($properties as $field => $value) {
|
|
|
|
|
self::setRawProperty($this, $field, $value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate all model properties
|
|
|
|
|
*
|
|
|
|
|
* @return array the validated values
|
|
|
|
|
*/
|
|
|
|
|
protected function validateAllValues(array $ignore = []): array
|
|
|
|
|
{
|
|
|
|
|
$out = [];
|
|
|
|
|
foreach (static::$validation as $field => $validationRules) {
|
|
|
|
|
if (\in_array($field, $ignore) or \is_null(self::getRawProperty($this, $field))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$this->validateValue($field, self::getRawProperty($this, $field));
|
|
|
|
|
$out[$field] = self::getRawProperty($this, $field);
|
|
|
|
|
}
|
|
|
|
|
return $out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate a value against its rules
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return boolean
|
|
|
|
|
* @throws TypeError
|
|
|
|
|
*/
|
|
|
|
|
protected static function validateValue(string $field, $value): bool
|
|
|
|
|
{
|
|
|
|
|
if (!isset(static::$validation[$field])) {
|
|
|
|
|
throw new Exception("$field has no validation rule");
|
|
|
|
|
}
|
|
|
|
|
$validator = Utils::makeValidator(
|
|
|
|
|
[$field => $value],
|
|
|
|
|
[$field => static::$validation[$field]]
|
|
|
|
|
);
|
2021-08-13 10:24:57 +02:00
|
|
|
if ($validator()) {
|
2021-08-09 09:18:46 +02:00
|
|
|
throw new ValidationException(implode(' ', $validator->errors()->get($field)));
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check a value against its field type
|
|
|
|
|
*
|
|
|
|
|
* @param string $type
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return boolean
|
|
|
|
|
* @throws Exception if type is not handled handled types are bool, string,
|
|
|
|
|
* int, \LDC\LDCCRM\Enums\Country, \LDC\LDCCRM\Enums\ArcadiaOrderType
|
|
|
|
|
*/
|
|
|
|
|
protected function checkFieldType(string $type, $value): bool
|
|
|
|
|
{
|
|
|
|
|
switch ($type) {
|
|
|
|
|
case '\\' . Country::class:
|
|
|
|
|
try {
|
|
|
|
|
$value = \is_int($value) ? Country::make($value) : $value;
|
|
|
|
|
} finally {
|
|
|
|
|
return \is_object($value) and (get_class($value) === Country::class or
|
|
|
|
|
\in_array(Country::class, \class_parents($value)));
|
|
|
|
|
}
|
2021-10-18 16:05:03 +02:00
|
|
|
case '\\' . QuestionType::class:
|
|
|
|
|
try {
|
|
|
|
|
$value = \is_int($value) ? QuestionType::make($value) : $value;
|
|
|
|
|
} finally {
|
|
|
|
|
return \is_object($value) and (get_class($value) === QuestionType::class or
|
|
|
|
|
\in_array(QuestionType::class, \class_parents($value)));
|
|
|
|
|
}
|
2021-08-09 09:18:46 +02:00
|
|
|
case '\\' . ArcadiaOrderType::class:
|
|
|
|
|
try {
|
|
|
|
|
$value = \is_int($value) ? ArcadiaOrderType::make($value) : $value;
|
|
|
|
|
} finally {
|
|
|
|
|
return \is_object($value) and (get_class($value) === ArcadiaOrderType::class or
|
|
|
|
|
\in_array(ArcadiaOrderType::class, \class_parents($value)));
|
|
|
|
|
}
|
|
|
|
|
case 'bool':
|
|
|
|
|
$value = \is_int($value) ? (bool)$value : $value;
|
|
|
|
|
return \is_bool($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);
|
|
|
|
|
case 'int':
|
|
|
|
|
return \is_int($value);
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception(\sprintf("Unhandled $type for %s", \json_encode($value)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
protected function getPropType(string $property)
|
|
|
|
|
{
|
|
|
|
|
$matches = [];
|
|
|
|
|
$propReflection = new ReflectionProperty(static::class, $property);
|
|
|
|
|
$docComment = $propReflection->getDocComment();
|
|
|
|
|
preg_match_all('/@var ([\w_\x5c]+)/', $docComment, $matches);
|
|
|
|
|
if (isset($matches[1][0]) and in_array($matches[1][0], [
|
2021-10-18 16:05:03 +02:00
|
|
|
'string', 'int', 'bool', 'array', '\\' . Country::class, '\\' . ArcadiaOrderType::class,
|
|
|
|
|
'\\' . QuestionType::class
|
2021-08-09 09:18:46 +02:00
|
|
|
], true)) {
|
|
|
|
|
return $matches[1][0];
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set a property withou any validation
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
protected static function setRawProperty(object $object, string $field, $value): void
|
|
|
|
|
{
|
|
|
|
|
if (is_object($value) and in_array(BaseEnum::class, class_parents($value))) {
|
|
|
|
|
$value = $value->value;
|
|
|
|
|
}
|
|
|
|
|
$refClass = new ReflectionClass(\get_class($object));
|
|
|
|
|
$refProp = $refClass->getProperty($field);
|
|
|
|
|
$refProp->setAccessible(true);
|
|
|
|
|
$refProp->setValue($object, $value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a property
|
|
|
|
|
*
|
|
|
|
|
* @param string $field
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return mixed
|
|
|
|
|
*/
|
|
|
|
|
protected static function getRawProperty(object $object, string $field)
|
|
|
|
|
{
|
|
|
|
|
$refClass = new ReflectionClass(\get_class($object));
|
|
|
|
|
$refProp = $refClass->getProperty($field);
|
|
|
|
|
$refProp->setAccessible(true);
|
|
|
|
|
return $refProp->getValue($object);
|
|
|
|
|
}
|
2021-08-11 17:26:30 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assert primaryKey is valid
|
|
|
|
|
*
|
|
|
|
|
* @param int $primaryKey
|
|
|
|
|
* @return void
|
|
|
|
|
* @throws LDC\LDCCRM\Exceptions\ValidationException
|
|
|
|
|
*/
|
|
|
|
|
protected static function assertValidPrimaryKey(int $primaryKey): void
|
|
|
|
|
{
|
|
|
|
|
$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')));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assert hashcode is valid
|
|
|
|
|
*
|
|
|
|
|
* @param string|null $hashcode
|
|
|
|
|
* @return void
|
|
|
|
|
* @throws LDC\LDCCRM\Exceptions\ValidationException
|
|
|
|
|
*/
|
|
|
|
|
protected static function assertValidHascode(?string $hashcode): void
|
|
|
|
|
{
|
|
|
|
|
$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
|
|
|
}
|