<?php
namespace App\Component\Configuration\Util;
use App\Component\Configuration\Cache\Adapter;
use App\Component\Configuration\Doctrine\DBAL\Types\EncryptedType;
use App\Component\Configuration\Doctrine\DBAL\Types\EntityType;
use App\Entity\ConfigurationSetting;
use App\Repository\ConfigurationSettingRepository;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Types\ArrayType;
use Doctrine\DBAL\Types\BooleanType;
use Doctrine\DBAL\Types\DateTimeType;
use Doctrine\DBAL\Types\FloatType;
use Doctrine\DBAL\Types\IntegerType;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
use RuntimeException;
use SpecShaper\EncryptBundle\Encryptors\EncryptorInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use TypeError;
class Config
{
public const BOOLEAN_TYPE = 'boolean';
public const STRING_TYPE = 'string';
public const DATETIME_TYPE = 'datetime';
public const ARRAY_TYPE = 'array';
public const INTEGER_TYPE = 'integer';
public const DOUBLE_TYPE = 'double';
public const ENTITY_TYPE = 'entity';
public const ENCRYPTED_TYPE = 'encrypted';
public const ACCEPTED_TYPES = [
self::BOOLEAN_TYPE => BooleanType::class,
self::STRING_TYPE => StringType::class,
self::DATETIME_TYPE => DateTimeType::class,
self::ARRAY_TYPE => ArrayType::class,
self::INTEGER_TYPE => IntegerType::class,
self::DOUBLE_TYPE => FloatType::class,
self::ENTITY_TYPE => EntityType::class,
self::ENCRYPTED_TYPE => EncryptedType::class,
];
private MySQLPlatform $platform;
/**
* Config constructor.
*/
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly Adapter $cacheAdapter,
private readonly ParameterBagInterface $parameterBag,
private readonly EntityManagerInterface $parentEntityManager,
private readonly EncryptorInterface $encryptor,
) {
$this->platform = new MySQLPlatform();
}
/**
* @return mixed
*/
public function get(string $name, string $connection = 'default')
{
if ($this->cacheAdapter->has($name)) {
return $this->cacheAdapter->get($name);
}
$setting = $this->getSetting($name);
$value = $this->getType($setting->getType(), $connection)->convertToPHPValue($setting->getValue(), $this->platform);
// don't cache a entity type because it is used as an managed entity
if (self::ENTITY_TYPE !== $setting->getType()) {
$this->cacheAdapter->set($name, $value);
}
return $value;
}
/**
* @param $value
*/
public function set(string $name, $value)
{
$setting = $this->getSetting($name);
$type = $setting->getType();
// in case of a string, string needs to be converted to object because of cache
if ('entity' === $type && 'string' === \gettype($value)) {
$value = $this->getType($type)->convertToPHPValue($value, $this->platform);
}
$databaseValue = $this->toDatabaseValueByType($value, $type);
if ($setting->getValue() === (null !== $databaseValue ? \strval($databaseValue) : null)) {
return;
}
$setting->setValue($databaseValue);
$this->entityManager->flush();
$this->cacheAdapter->set($name, $value);
}
public function getBoolean(string $name): bool
{
$result = $this->get($name);
return \is_bool($result) ? $result : false;
}
public function getConfigIfTypeStringElseEmptyString(string $name): string
{
return $this->getConfigIfTypeStringElseNull($name) ?? '';
}
public function getConfigIfTypeStringElseNull(string $name): ?string
{
$result = $this->get($name);
return \is_string($result) ? $result : null;
}
/**
* @return array<int|string, mixed>
*/
public function getConfigIfTypeArrayElseEmptyArray(string $name): array
{
return $this->getConfigIfTypeArrayElseNull($name) ?? [];
}
/**
* @return array<int|string, mixed>|null
*/
public function getConfigIfTypeArrayElseNull(string $name): ?array
{
$result = $this->get($name);
return \is_array($result) ? $result : null;
}
protected function getSetting(string $name): ConfigurationSetting
{
if ($setting = $this->getRepository()->findOneBy(['name' => $name])) {
return $setting;
}
// Temporary paramaterBag-fallback to cover unmigrated settings
// (mainly to support LocalisedSettingService)
// @todo : can be removed after migrating all parameters
if ($this->parameterBag->has($name)) {
$parameter = $this->parameterBag->get($name);
$parameterType = \gettype($parameter);
$configType = self::STRING_TYPE;
if ('NULL' === $parameterType) {
$parameterType = self::STRING_TYPE;
}
switch ($parameterType) {
case 'boolean':
$configType = self::BOOLEAN_TYPE;
break;
case 'integer':
$configType = self::INTEGER_TYPE;
break;
case 'array':
$configType = self::ARRAY_TYPE;
break;
}
return (new ConfigurationSetting())
->setName($name)
->setValue($this->toDatabaseValueByType($parameter, $parameterType))
->setType($configType)
;
}
throw $this->createNotFoundException($name);
}
public function getType(string $settingType, string $connection = 'default'): ?Type
{
if (!\array_key_exists($settingType, self::ACCEPTED_TYPES)) {
return null;
}
$typeClass = self::ACCEPTED_TYPES[$settingType];
$type = new $typeClass();
if (method_exists($type, 'setEntityManager')) {
$entityManager = $this->entityManager;
if ('parent' === $connection) {
$entityManager = $this->parentEntityManager;
}
$type->setEntityManager($entityManager);
}
if (method_exists($type, 'setEncryptor')) {
$type->setEncryptor($this->encryptor);
}
return $type;
}
/**
* @param string $section name of the section to fetch settings for
* @param bool $fresh is set to true, will force fresh (uncached and managed data)
*
* @return array with name => value
*/
public function getBySection(string $section, bool $fresh = false): array
{
$configurationSettingCollection = $this->getRepository()->findBy(['section' => $section]);
return $this->transformCollectionToKeyValue($configurationSettingCollection, $fresh);
}
/**
* @param bool $fresh is set to true, will force fresh (uncached and managed data)
*
* @return array with name => value
*/
public function getAll(bool $fresh = false): array
{
$configurationSettingCollection = $this->getRepository()->findAll();
return $this->transformCollectionToKeyValue($configurationSettingCollection, $fresh);
}
/**
* @param $value
*
* @return mixed
*/
protected function toDatabaseValueByType($value, string $type)
{
$valueType = \gettype($value);
if ('entity' === $type && \in_array($valueType, ['object', 'array', 'string'], true)) {
$valueType = 'entity';
}
if ('encrypted' === $type && 'string' === $valueType) {
$valueType = 'encrypted';
}
if ('object' === $valueType) {
$valueType = mb_strtolower(\get_class($value));
}
if (null !== $value && $valueType !== $type) {
throw new TypeError(sprintf('setting must be of the type %s, %s given', $type, $valueType));
}
return $this->getType($type)->convertToDatabaseValue($value, $this->platform);
}
/**
* @return ConfigurationSettingRepository|ObjectRepository
*/
protected function getRepository(): ObjectRepository
{
return $this->entityManager->getRepository(ConfigurationSetting::class);
}
/**
* @param string $name name of the setting
*/
protected function createNotFoundException(string $name): RuntimeException
{
return new RuntimeException(sprintf('Setting "%s" couldn\'t be found.', $name));
}
/**
* Get a array list of all available config settings.
*
* @return array list of all config settings
*/
public function getList(): array
{
return $this->getRepository()->getList();
}
/**
* @param bool $fresh is set to true, will force fresh (uncached and managed data)
*
* @return array with name => value
*/
private function transformCollectionToKeyValue(array $configurationSettingCollection, bool $fresh = false): array
{
$output = [];
foreach ($configurationSettingCollection as $setting) {
$name = $setting->getName();
$output[$name] = (false === $fresh && $this->cacheAdapter->has($name))
? $this->cacheAdapter->get($name)
: $this->getType($setting->getType())->convertToPHPValue($setting->getValue(), $this->platform)
;
}
return $output;
}
}