diff --git a/composer.json b/composer.json
index f9b07cf..597099d 100644
--- a/composer.json
+++ b/composer.json
@@ -78,7 +78,8 @@
"bin": ["bin/tray-digita"],
"autoload": {
"psr-4": {
- "ArrayAccess\\TrayDigita\\": "src/"
+ "ArrayAccess\\TrayDigita\\": "src/",
+ "ArrayAccess\\TrayDigita\\App\\Modules\\": "app/Modules"
}
}
}
diff --git a/src/Benchmark/Formatter/HtmlFormatter.php b/src/Benchmark/Formatter/HtmlFormatter.php
index 4a63011..4807c27 100644
--- a/src/Benchmark/Formatter/HtmlFormatter.php
+++ b/src/Benchmark/Formatter/HtmlFormatter.php
@@ -155,8 +155,8 @@ public function format(MetadataCollectionInterface $metadataCollection): Formatt
$type = gettype($i);
$i = (string) $i;
$length = strlen($i);
- $i = strlen($i) > 300
- ? substr($i, 0, 300) . '...' : $i;
+ $i = strlen($i) > 1024
+ ? substr($i, 0, 1024) . '...' : $i;
$pre .= sprintf('%s: (size=%d
) %s', $type, $length, htmlentities($i));
} elseif (is_array($i)) {
$pre .= sprintf('array: (size=%d
)', count($i));
diff --git a/src/Cache/Entities/CacheItem.php b/src/Cache/Entities/CacheItem.php
new file mode 100644
index 0000000..82d2553
--- /dev/null
+++ b/src/Cache/Entities/CacheItem.php
@@ -0,0 +1,143 @@
+ 'utf8mb4', // remove this or change to utf8 if not use mysql
+ 'collation' => 'utf8mb4_unicode_ci', // remove this if not use mysql
+ 'comment' => 'Cache items'
+ ]
+)]
+#[HasLifecycleCallbacks]
+class CacheItem extends AbstractEntity
+{
+ const TABLE_NAME = 'cache_items';
+
+ #[Id]
+ #[Column(
+ name: 'item_id',
+ type: Types::BINARY,
+ length: 255,
+ options: [
+ 'comment' => 'Item id'
+ ]
+ )]
+ protected string $item_id;
+
+ #[Column(
+ name: 'item_data',
+ type: Types::BLOB,
+ length: AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB,
+ options: [
+ 'comment' => 'Cache item data'
+ ]
+ )]
+ protected mixed $item_data;
+
+ #[Column(
+ name: 'item_lifetime',
+ type: Types::INTEGER,
+ length: 10,
+ nullable: true,
+ options: [
+ 'unsigned' => true,
+ 'comment' => 'Cache item lifetime'
+ ]
+ )]
+ protected ?int $item_lifetime;
+
+ #[Column(
+ name: 'item_time',
+ type: Types::INTEGER,
+ length: 10,
+ options: [
+ 'unsigned' => true,
+ 'comment' => 'Cache item timestamp'
+ ]
+ )]
+ protected int $item_time;
+
+ public function getItemId(): string
+ {
+ return $this->item_id;
+ }
+
+ public function setItemId(string $item_id): void
+ {
+ $this->item_id = $item_id;
+ }
+
+ public function getItemData(): mixed
+ {
+ return $this->item_data;
+ }
+
+ public function setItemData(mixed $item_data): void
+ {
+ $this->item_data = $item_data;
+ }
+
+ public function getItemLifetime(): ?int
+ {
+ return $this->item_lifetime;
+ }
+
+ public function setItemLifetime(?int $item_lifetime): void
+ {
+ $this->item_lifetime = $item_lifetime;
+ }
+
+ public function getItemTime(): int
+ {
+ return $this->item_time;
+ }
+
+ public function setItemTime(int $item_time): void
+ {
+ $this->item_time = $item_time;
+ }
+
+ public static function addEntityToMetadata(
+ Connection|EntityManagerInterface $connection
+ ): void {
+ if ($connection instanceof Connection) {
+ $connection = $connection->getEntityManager();
+ }
+ $metadataFactory = $connection->getMetadataFactory();
+ if ($metadataFactory->hasMetadataFor(__CLASS__)) {
+ return;
+ }
+ $configuration = $connection->getConfiguration();
+ $metadataFactory->setMetadataFor(
+ __CLASS__,
+ new ClassMetadata(
+ __CLASS__,
+ $configuration->getNamingStrategy(),
+ $configuration->getTypedFieldMapper()
+ )
+ );
+ }
+}
diff --git a/src/Console/Command/ApplicationChecker/CacheChecker.php b/src/Console/Command/ApplicationChecker/CacheChecker.php
index 5521a63..de33863 100644
--- a/src/Console/Command/ApplicationChecker/CacheChecker.php
+++ b/src/Console/Command/ApplicationChecker/CacheChecker.php
@@ -3,7 +3,7 @@
namespace ArrayAccess\TrayDigita\Console\Command\ApplicationChecker;
-use ArrayAccess\TrayDigita\Cache\Cache;
+use ArrayAccess\TrayDigita\Cache\Entities;
use ArrayAccess\TrayDigita\Collection\Config;
use ArrayAccess\TrayDigita\Console\Command\Traits\WriterHelperTrait;
use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper;
diff --git a/src/Console/Command/DatabaseChecker.php b/src/Console/Command/DatabaseChecker.php
index c25db86..337fbdb 100644
--- a/src/Console/Command/DatabaseChecker.php
+++ b/src/Console/Command/DatabaseChecker.php
@@ -23,6 +23,12 @@
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\SchemaDiff;
+use Doctrine\DBAL\Schema\SchemaException;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Types\Type;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\ORM\EntityManager;
@@ -40,6 +46,7 @@
use function array_change_key_case;
use function array_filter;
use function array_map;
+use function array_merge;
use function end;
use function explode;
use function implode;
@@ -50,11 +57,14 @@
use function memory_get_peak_usage;
use function memory_get_usage;
use function microtime;
+use function preg_match;
use function preg_replace;
use function round;
use function rtrim;
use function sprintf;
+use function str_starts_with;
use function strtolower;
+use function substr_replace;
use function trim;
use const ARRAY_FILTER_USE_KEY;
use const PHP_BINARY;
@@ -356,8 +366,8 @@ public function databaseSchemaDetect(InputInterface $input, OutputInterface $out
}
if ($optionSchemaDump) {
- $sql = $currentSchema->toSql($platform);
- if (empty($sql)) {
+ $schemaSQL = $currentSchema->toSql($platform);
+ if (empty($schemaSQL)) {
$output->writeln(
sprintf(
'%s',
@@ -368,7 +378,7 @@ public function databaseSchemaDetect(InputInterface $input, OutputInterface $out
}
$output->writeln('');
$formatter = new SqlFormatter(new CliHighlighter());
- foreach ($sql as $query) {
+ foreach ($schemaSQL as $query) {
$query = rtrim($query, ';');
$output->writeln(
$formatter->format("$query;"),
@@ -388,8 +398,9 @@ public function databaseSchemaDetect(InputInterface $input, OutputInterface $out
$clonedSchema,
$currentSchema
);
- $sql = $platform->getAlterSchemaSQL($schemaDiff);
- if (empty($sql)) {
+ $this->compareSchemaFix($currentSchema, $clonedSchema, $schemaDiff);
+ $schemaSQL = $this->getAlterSQL($schemaDiff, $platform);
+ if (empty($schemaSQL)) {
$output->writeln(
sprintf(
'%s',
@@ -400,7 +411,7 @@ public function databaseSchemaDetect(InputInterface $input, OutputInterface $out
}
$output->writeln('');
$formatter = new SqlFormatter(new CliHighlighter());
- foreach ($sql as $query) {
+ foreach ($schemaSQL as $query) {
$query = rtrim($query, ';');
$output->writeln(
$formatter->format("$query;"),
@@ -451,6 +462,7 @@ public function databaseSchemaDetect(InputInterface $input, OutputInterface $out
}
$currentTable = $currentSchema->getTable($meta->getTableName());
$tableDiff = $comparator->compareTables($table, $currentTable);
+ $this->compareSchemaTableFix($table, $currentTable, $tableDiff);
$isNeedOptimize = ($optimizeArray[strtolower($currentTable->getName())] ?? 0) > 0;
if ($isNeedOptimize) {
$containOptimize = true;
@@ -1352,7 +1364,7 @@ function ($e) {
$clonedSchema,
$currentSchema
);
-
+ $this->compareSchemaFix($currentSchema, $clonedSchema, $schemaDiff);
if ($schemaDiff->isEmpty()) {
$output->writeln(
sprintf(
@@ -1363,7 +1375,7 @@ function ($e) {
return Command::SUCCESS;
}
- $schemaSQL = $platform->getAlterSchemaSQL($schemaDiff);
+ $schemaSQL = $this->getAlterSQL($schemaDiff, $platform);
if (empty($schemaSQL)) {
$output->writeln(
sprintf(
@@ -1458,22 +1470,22 @@ function ($e) {
$progressBar = !$output->isVerbose() ? $io->createProgressBar() : null;
$progressBar?->setMaxSteps(count($schemaSQL));
try {
- foreach ($schemaSQL as $sql) {
+ foreach ($schemaSQL as $sqlQuery) {
$output->writeln(
sprintf(
'%s %s',
$this->translateContext('Executing:', 'console'),
- $sql
+ $sqlQuery
),
OutputInterface::VERBOSITY_VERY_VERBOSE
);
$progressBar?->advance();
if ($is_pdo) {
- $conn->exec($sql);
+ $conn->exec($sqlQuery);
continue;
}
try {
- $conn->executeStatement($sql);
+ $conn->executeStatement($sqlQuery);
} catch (Throwable $e) {
$progressBar?->finish();
$progressBar?->clear();
@@ -1880,6 +1892,7 @@ protected function doCheckData(
$comparator = $schema->createComparator();
$tableDb = $schema->introspectTable($meta->getTableName());
$tableDiff = $comparator->compareTables($tableDb, $currentTable);
+ $this->compareSchemaTableFix($tableDb, $currentTable, $tableDiff);
if (!empty($tableDiff->changedColumns)
|| !empty($tableDiff->renamedColumns)
|| !empty($tableDiff->removedColumns)
@@ -1988,4 +2001,91 @@ protected function doCheckData(
$output->writeln('');
}
}
+
+ private function compareSchemaTableFix(Table $realTable, Table $currentTable, TableDiff $diff): void
+ {
+ foreach ($currentTable->getForeignKeys() as $foreignKey) {
+ if (!$foreignKey->hasOption('oldName')) {
+ continue;
+ }
+ $oldName = $foreignKey->getOption('oldName');
+
+ if (!str_starts_with($oldName, 'fk_')) {
+ continue;
+ }
+
+ // fk_ to idx_
+ $oldName = substr_replace($oldName, 'idx_', 0, 3);
+ if (!$realTable->hasIndex($oldName)) {
+ continue;
+ }
+ $name = $foreignKey->getName();
+ if (!isset($diff->renamedIndexes[$oldName])) {
+ continue;
+ }
+
+ $data = $diff->renamedIndexes[$oldName];
+ unset($diff->renamedIndexes[$oldName]);
+ if (!isset($diff->addedIndexes[$name]) && !$realTable->hasIndex($name)) {
+ $diff->addedIndexes[$name] = $data;
+ }
+ }
+ }
+
+ private function compareSchemaFix(Schema $currentSchema, Schema $realSchema, SchemaDiff $diff): void
+ {
+ if (!empty($diff->changedTables)) {
+ foreach ($diff->changedTables as $k => $tableDiff) {
+ if (!$tableDiff instanceof TableDiff) {
+ continue;
+ }
+ $theTable = $tableDiff->fromTable??null;
+ if (!$theTable instanceof Table) {
+ continue;
+ }
+ if (!$realSchema->hasTable($theTable->getName())) {
+ continue;
+ }
+ try {
+ $this->compareSchemaTableFix(
+ $theTable,
+ $currentSchema->getTable($theTable->getName()),
+ $tableDiff
+ );
+ } catch (SchemaException) {
+ }
+ $diff->changedTables[$k] = $tableDiff;
+ }
+ }
+ }
+
+ /**
+ * @return array
+ */
+ protected function getAlterSQL(SchemaDiff $diff, AbstractPlatform $platform): array
+ {
+ $sql = $platform->getAlterSchemaSQL($diff);
+ // we don't use event subscriber
+ $constraint = [];
+ $createOrAddIndex = [];
+ $dropIndex = [];
+ foreach ($sql as $key => $query) {
+ if (preg_match('~^\s*(CREATE|ADD|RENAME).+INDEX~i', $query)) {
+ $createOrAddIndex[] = $query;
+ unset($sql[$key]);
+ continue;
+ }
+ if (preg_match('~^\s*ALTER\s+(ADD|REMOVE).+CONSTRAINT~i', $query)) {
+ $constraint[] = $query;
+ unset($sql[$key]);
+ continue;
+ }
+ if (preg_match('~DROP.+INDEX~i', $query)) {
+ $dropIndex[] = $query;
+ unset($sql[$key]);
+ }
+ }
+
+ return array_merge($sql, $dropIndex, $createOrAddIndex, $constraint);
+ }
}
diff --git a/src/Container/Factory/ContainerFactory.php b/src/Container/Factory/ContainerFactory.php
index 14dc6ea..eb3b3aa 100644
--- a/src/Container/Factory/ContainerFactory.php
+++ b/src/Container/Factory/ContainerFactory.php
@@ -18,7 +18,7 @@
use ArrayAccess\TrayDigita\Benchmark\Interfaces\ProfilerInterface;
use ArrayAccess\TrayDigita\Benchmark\Profiler;
use ArrayAccess\TrayDigita\Benchmark\Waterfall;
-use ArrayAccess\TrayDigita\Cache\Cache;
+use ArrayAccess\TrayDigita\Cache\Entities;
use ArrayAccess\TrayDigita\Collection\Config;
use ArrayAccess\TrayDigita\Console\Application;
use ArrayAccess\TrayDigita\Container\Container;
diff --git a/src/Database/Connection.php b/src/Database/Connection.php
index 0754274..17e9d15 100644
--- a/src/Database/Connection.php
+++ b/src/Database/Connection.php
@@ -5,6 +5,7 @@
use ArrayAccess\TrayDigita\Collection\Config;
use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface;
+use ArrayAccess\TrayDigita\Database\Factory\MetadataFactory;
use ArrayAccess\TrayDigita\Database\Wrapper\DriverWrapper;
use ArrayAccess\TrayDigita\Database\Wrapper\EntityManagerWrapper;
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface;
@@ -13,6 +14,7 @@
use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait;
use ArrayAccess\TrayDigita\Util\Filter\Consolidation;
use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper;
+use ArrayAccess\TrayDigita\Util\Filter\DataNormalizer;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
@@ -31,6 +33,7 @@
use SensitiveParameter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
+use function array_keys;
use function is_dir;
use function is_string;
use function is_subclass_of;
@@ -38,6 +41,7 @@
use function mkdir;
use function preg_match;
use function preg_replace;
+use function realpath;
use function strtolower;
use function trim;
@@ -157,8 +161,14 @@ protected function configureORMConfiguration(?Configuration $configuration) : Or
? $proxyNamesSpace
: preg_replace('~(.+)\\\[^\\\]+$~', '$1\\Storage\Proxy', __NAMESPACE__);
if (!$metadata instanceof AttributeDriver) {
+ $entityDirectories = [];
+ if (is_string($entityDirectory) && is_dir($entityDirectory)) {
+ $entityDirectories[] = realpath($entityDirectory)??
+ DataNormalizer::normalizeUnixDirectorySeparator($entityDirectory, true);
+ }
+
$metadata = new AttributeDriver(
- [$entityDirectory],
+ $entityDirectories,
true
);
}
@@ -168,6 +178,7 @@ protected function configureORMConfiguration(?Configuration $configuration) : Or
$orm->setMetadataDriverImpl($metadata);
$orm->setProxyDir($proxyPath);
$orm->setProxyNamespace($proxyNamesSpace);
+ $orm->setClassMetadataFactoryName(MetadataFactory::class);
// @dispatch(database.configureORMConfiguration)
$this->dispatchCurrent($orm);
return $orm;
@@ -177,6 +188,31 @@ protected function configureORMConfiguration(?Configuration $configuration) : Or
}
}
+ public function registerEntityDirectory(string ...$directories): ?array
+ {
+ $driver = $this->getDefaultConfiguration()->getMetadataDriverImpl();
+ if ($driver instanceof AttributeDriver) {
+ $toRegistered = [];
+ foreach ($directories as $directory) {
+ if (!is_dir($directory)) {
+ continue;
+ }
+ $directory = realpath($directory);
+ if (!$directory) {
+ continue;
+ }
+ $toRegistered[$directory] = true;
+ }
+ if (empty($toRegistered)) {
+ return null;
+ }
+ $toRegistered = array_keys($toRegistered);
+ $driver->addPaths($toRegistered);
+ return $toRegistered;
+ }
+ return null;
+ }
+
public function getDatabaseConfig() : Config
{
return $this->configureDatabaseConfig();
diff --git a/src/Database/Entities/Abstracts/AbstractEntity.php b/src/Database/Entities/Abstracts/AbstractEntity.php
index d57bef5..ac24ae9 100644
--- a/src/Database/Entities/Abstracts/AbstractEntity.php
+++ b/src/Database/Entities/Abstracts/AbstractEntity.php
@@ -3,6 +3,7 @@
namespace ArrayAccess\TrayDigita\Database\Entities\Abstracts;
+use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface;
use ArrayAccess\TrayDigita\Database\Connection;
use ArrayAccess\TrayDigita\Database\Entities\Traits\FieldNameGetter;
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface;
@@ -258,6 +259,7 @@ protected function maskArray(): array
$this->getEntityBlacklistedFields(),
'is_string'
);
+
$aliases = $this->getEntityFieldAliases();
$fields = $this->getEntityDisplayFields();
$className = $this::class;
@@ -268,28 +270,33 @@ protected function maskArray(): array
}
// unset($fields['fieldsarray']);
} else {
+ $container = $this instanceof ContainerIndicateInterface
+ ? $this->getContainer()
+ : null;
$columns = self::$columnsFields[$className];
// fallback default
- $em = $this->getEntityManager()
- ??ContainerHelper::use(Connection::class)
+ $em = $this
+ ->getEntityManager()
+ ??ContainerHelper::use(Connection::class, $container)
?->getEntityManager();
if ($em && empty($columns)) {
- $metadata = $em->getClassMetadata($className);
- foreach ($metadata->getFieldNames() as $item) {
- try {
- $mapping = $metadata->getFieldMapping($item);
- } catch (MappingException) {
- continue;
- }
- /** @noinspection DuplicatedCode */
- $fieldName = $mapping['fieldName']??null;
- $columnName = $mapping['columnName']??null;
- if (!$fieldName || !$columnName) {
- continue;
+ foreach ($em->getMetadataFactory()->getAllMetadata() as $metadata) {
+ foreach ($metadata->getFieldNames() as $item) {
+ try {
+ $mapping = $metadata->getFieldMapping($item);
+ } catch (MappingException) {
+ continue;
+ }
+ /** @noinspection DuplicatedCode */
+ $fieldName = $mapping['fieldName']??null;
+ $columnName = $mapping['columnName']??null;
+ if (!$fieldName || !$columnName) {
+ continue;
+ }
+ $columnName = strtolower($columnName);
+ $columns[$fieldName] = $fieldName;
+ self::$columnsFields[$className][$columnName] = $fieldName;
}
- $columnName = strtolower($columnName);
- $columns[$fieldName] = $fieldName;
- self::$columnsFields[$className][$columnName] = $fieldName;
}
}
foreach ($columns as $item) {
diff --git a/src/Database/Entities/Abstracts/AbstractUser.php b/src/Database/Entities/Abstracts/AbstractUser.php
index babda8e..7b98f31 100644
--- a/src/Database/Entities/Abstracts/AbstractUser.php
+++ b/src/Database/Entities/Abstracts/AbstractUser.php
@@ -80,8 +80,6 @@ abstract class AbstractUser extends AbstractEntity implements
// MAXIMUM AUTH PERIOD is 10 minutes
const MAX_AUTH_PERIOD = 600;
- protected array $availableRoles = [];
-
#[Id]
#[GeneratedValue('AUTO')]
#[Column(
@@ -303,11 +301,6 @@ public function __construct()
$this->security_key = RandomString::char(128);
}
- public function getAvailableRoles(): array
- {
- return $this->availableRoles;
- }
-
public static function generateAuthCode(
string|int|float $prefix = ''
): string {
diff --git a/src/Database/Entities/Traits/FieldNameGetter.php b/src/Database/Entities/Traits/FieldNameGetter.php
index b60a7ec..a694037 100644
--- a/src/Database/Entities/Traits/FieldNameGetter.php
+++ b/src/Database/Entities/Traits/FieldNameGetter.php
@@ -3,21 +3,10 @@
namespace ArrayAccess\TrayDigita\Database\Entities\Traits;
-use ArrayAccess\TrayDigita\Database\Entities\Abstracts\AbstractEntity;
-use Doctrine\ORM\Mapping\Column;
-use Doctrine\ORM\Mapping\Id;
-use Doctrine\ORM\Mapping\JoinColumn;
-use Doctrine\ORM\Mapping\JoinTable;
-use Doctrine\ORM\Mapping\ManyToMany;
-use Doctrine\ORM\Mapping\ManyToOne;
-use Doctrine\ORM\Mapping\MappingAttribute;
-use Doctrine\ORM\Mapping\OneToMany;
-use Doctrine\ORM\Mapping\OneToOne;
-use ReflectionAttribute;
-use ReflectionObject;
+use ArrayAccess\TrayDigita\Database\Connection;
+use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Throwable;
-use function array_diff;
-use function array_map;
use function in_array;
use function is_string;
use function str_replace;
@@ -35,101 +24,52 @@ trait FieldNameGetter
// reference for set
private static array $objectMethodSetFields = [];
- private function configureMethodsParameterFields(): void
- {
- $className = $this::class;
- if (!isset(self::$objectFields[$className])) {
- /** @noinspection PhpInstanceofIsAlwaysTrueInspection */
- $isAbstractEntity = $this instanceof AbstractEntity;
- self::$objectFields[$className] = [];
+ private function createStaticCache(
+ ClassMetadataInfo $metadata
+ ): void {
+ $reflection = $metadata->getReflectionClass();
+ $className = $reflection->getName();
+ if (!isset(self::$columnsFields[$className])) {
self::$columnsFields[$className] = [];
- $ref = new ReflectionObject($this);
- $prop = [];
- $allowedMapping = [
- Id::class,
- JoinColumn::class,
- ManyToOne::class,
- ManyToMany::class,
- OneToOne::class,
- OneToMany::class,
- Column::class,
- JoinTable::class,
- ];
- $countAllowedMapping = count($allowedMapping);
- foreach ($ref->getProperties() as $property) {
- if ($property->isPrivate()) {
- continue;
- }
- $attr = array_map(fn ($e) => $e->getname(), $property
- ->getAttributes(
- MappingAttribute::class,
- ReflectionAttribute::IS_INSTANCEOF
- ));
- if (empty($attr)) {
+ self::$objectFields[$className] = [];
+ foreach ($metadata->getFieldNames() as $item) {
+ try {
+ $mapping = $metadata->getFieldMapping($item);
+ } catch (Throwable) {
continue;
}
- $count = array_diff($allowedMapping, $attr);
- if (count($count) === $countAllowedMapping) {
+ /** @noinspection DuplicatedCode */
+ $fieldName = $mapping['fieldName'] ?? null;
+ $columnName = $mapping['columnName'] ?? null;
+ if (!$fieldName || !$columnName) {
continue;
}
- $name = $property->getName();
- $key = strtolower($name);
- $prop[$key] = $name;
+ $columnName = strtolower($columnName);
+ self::$objectFields[$className][$columnName] = $fieldName;
+ self::$columnsFields[$className][$columnName] = $fieldName;
}
-
- if ($isAbstractEntity
- && ($metadata = $this
- ->getEntityManager()
- ?->getClassMetadata($this::class))
- ) {
- $prop = [];
- foreach ($metadata->getFieldNames() as $item) {
- try {
- $mapping = $metadata->getFieldMapping($item);
- } catch (Throwable) {
- continue;
- }
- /** @noinspection DuplicatedCode */
- $fieldName = $mapping['fieldName']??null;
- $columnName = $mapping['columnName']??null;
- if (!$fieldName || !$columnName) {
- continue;
- }
- $columnName = strtolower($columnName);
- $prop[$columnName] = $fieldName;
- self::$columnsFields[$className][$columnName] = $fieldName;
- }
-
- foreach ($metadata->getAssociationMappings() as $associationMapping) {
- $fieldName = $associationMapping['fieldName']??null;
- if (!is_string($fieldName)
- || !$ref->hasProperty($fieldName)
- || $ref->getProperty($fieldName)->isPrivate()
- ) {
- continue;
- }
-
- $lower = strtolower($fieldName);
- $prop[$lower] ??= $ref->getProperty($fieldName)->getName();
+ foreach ($metadata->getAssociationMappings() as $associationMapping) {
+ $fieldName = $associationMapping['fieldName'] ?? null;
+ if (!is_string($fieldName)
+ || !$reflection->hasProperty($fieldName)
+ || $reflection->getProperty($fieldName)->isPrivate()
+ ) {
+ continue;
}
+ $lower = strtolower($fieldName);
+ self::$objectFields[$className][$lower] ??= $reflection->getProperty($fieldName)->getName();
}
-
- self::$objectFields[$className] = $prop;
}
- if (!isset(self::$objectMethodIsGetFields[$className])
- || !isset(self::$objectMethodSetFields[$className])
- ) {
+ if (!isset(self::$objectMethodIsGetFields[$className])) {
$getMethods = [];
$setMethods = [];
- $ref ??= new ReflectionObject($this);
- foreach ($ref->getMethods() as $method) {
+ foreach ($reflection->getMethods() as $method) {
if (!$method->isPublic()
|| $method->getNumberOfRequiredParameters() > 0
) {
continue;
}
-
$methodName = $method->getName();
$lowerMethodName = strtolower($methodName);
$substrStart = str_starts_with($lowerMethodName, 'get')
@@ -152,6 +92,17 @@ private function configureMethodsParameterFields(): void
}
}
+ private function configureMethodsParameterFields(): void
+ {
+ $className = $this::class;
+ if (isset(self::$objectFields[$className])) {
+ return;
+ }
+ $em = $this->getEntityManager()
+ ?? ContainerHelper::service(Connection::class)->getEntityManager();
+ $this->createStaticCache($em->getClassMetadata($className));
+ }
+
public function get(string $name, &$found = null)
{
$found = false;
diff --git a/src/Database/Events/CreateSchemaToolsEvent.php b/src/Database/Events/CreateSchemaToolsEvent.php
index 4aab645..2485b49 100644
--- a/src/Database/Events/CreateSchemaToolsEvent.php
+++ b/src/Database/Events/CreateSchemaToolsEvent.php
@@ -165,14 +165,14 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
}
$validName = is_string($relationName) && $relationName !== '';
-
$targetTable = $this
->connection
->getEntityManager()
->getClassMetadata($association['targetEntity'])
->getTableName();
+ $removedForeign = [];
foreach ($foreignKeys as $foreignName => $foreignKey) {
- $name = $foreignKey->getName();
+ $name = is_string($foreignName) ? $foreignName : $foreignKey->getName();
if (!str_starts_with(strtolower($name), 'fk_')
|| $foreignKey->getForeignTableName() !== $targetTable
) {
@@ -198,13 +198,16 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
$opt['onDelete'] = $onDelete;
}
try {
+ $opt['oldName'] = $foreignName;
+ $validName = $validName ? $relationName : $foreignName;
$table->removeForeignKey($foreignName);
+ $removedForeign[$name] = $validName;
$table->addForeignKeyConstraint(
$foreignKey->getForeignTableName(),
$foreignKey->getLocalColumns(),
$foreignKey->getForeignColumns(),
$opt,
- $validName ? $relationName : $foreignName
+ $validName
);
} catch (Throwable) {
}
diff --git a/src/Database/Factory/MetadataFactory.php b/src/Database/Factory/MetadataFactory.php
new file mode 100644
index 0000000..90f3c53
--- /dev/null
+++ b/src/Database/Factory/MetadataFactory.php
@@ -0,0 +1,40 @@
+table['options']['priority'])) {
+ $classMetadata->table['options']['priority'] = 10;
+ }
+ $metadata[$classMetadata->table['name']] = $classMetadata;
+ }
+ ksort($metadata);
+ uasort(
+ $metadata,
+ function (ClassMetadataInfo $a, ClassMetadataInfo $b) {
+ $a = $a->table['options']['priority'];
+ $b = $b->table['options']['priority'];
+ if ($a === $b || !is_numeric($a) || !is_numeric($b)) {
+ return 0;
+ }
+ return $a < $b ? -1 : 1;
+ }
+ );
+ return $metadata;
+ }
+}
diff --git a/src/Database/Result/AbstractRepositoryFinder.php b/src/Database/Result/AbstractRepositoryFinder.php
index 9586824..f59f31b 100644
--- a/src/Database/Result/AbstractRepositoryFinder.php
+++ b/src/Database/Result/AbstractRepositoryFinder.php
@@ -3,6 +3,7 @@
namespace ArrayAccess\TrayDigita\Database\Result;
+use ArrayAccess\TrayDigita\App\Modules\Users\Entities\Site;
use ArrayAccess\TrayDigita\Database\Connection;
use ArrayAccess\TrayDigita\Database\Entities\Abstracts\AbstractEntity;
use ArrayAccess\TrayDigita\Database\Helper\Expression;
@@ -48,6 +49,7 @@ abstract public function getRepository() : ObjectRepository&Selectable;
/**
* @param string $searchQuery
+ * @param int|Site|null $site
* @param int $limit
* @param int $offset
* @param array> $orderBy
@@ -58,6 +60,7 @@ abstract public function getRepository() : ObjectRepository&Selectable;
*/
public function search(
string $searchQuery,
+ int|Site|null $site = null,
int $limit = 10,
int $offset = 0,
array $orderBy = [],
diff --git a/src/HttpKernel/BaseKernel.php b/src/HttpKernel/BaseKernel.php
index c57b0f9..f8bd4dd 100644
--- a/src/HttpKernel/BaseKernel.php
+++ b/src/HttpKernel/BaseKernel.php
@@ -37,6 +37,7 @@
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Yaml\Yaml;
use Throwable;
+use function array_keys;
use function date_default_timezone_get;
use function date_default_timezone_set;
use function define;
@@ -100,6 +101,11 @@ abstract class BaseKernel implements
*/
protected array $registeredDirectories = [];
+ /**
+ * @var array
+ */
+ protected array $controllersDirectories = [];
+
/**
* @var array|string[]
*/
@@ -132,6 +138,38 @@ public function __construct(
}
}
+ public function registerControllerDirectory(string ...$directory): array
+ {
+ foreach ($directory as $dir) {
+ if (!is_dir($dir)) {
+ continue;
+ }
+ $this->controllersDirectories[realpath($dir)] = $dir;
+ }
+ return $this->controllersDirectories;
+ }
+
+ public function getControllersDirectories(): array
+ {
+ return array_keys($this->controllersDirectories);
+ }
+
+ public function removeControllerDirectory(string ...$directory): array
+ {
+ $removed = [];
+ foreach ($directory as $dir) {
+ if (!is_dir($dir)) {
+ continue;
+ }
+ $dir = realpath($dir);
+ if (isset($this->controllersDirectories[$dir])) {
+ unset($this->controllersDirectories[$dir]);
+ $removed[] = $dir;
+ }
+ }
+ return $removed;
+ }
+
/**
* @return ?ContainerInterface
*/
@@ -808,6 +846,11 @@ final public function init() : static
// @dispatch(kernel.afterInitConfig)
$manager->dispatch('kernel.afterInitConfig', $this);
}
+ if (isset($this->registeredDirectories[$this->controllerNameSpace])) {
+ $this->registerControllerDirectory(
+ $this->registeredDirectories[$this->controllerNameSpace]
+ );
+ }
// do register namespace first
$this->registerAutoloaderNameSpace();
@@ -868,21 +911,14 @@ protected function registerProviders(): void
} catch (Throwable) {
}
}
- $poMoAdapter ??= new PoMoAdapter();
- $jsonAdapter ??= new JsonAdapter();
+ $poMoAdapter ??= new PoMoAdapter($translator);
+ $jsonAdapter ??= new JsonAdapter($translator);
$translator->addAdapter($poMoAdapter);
$translator->addAdapter($jsonAdapter);
$languageDirectory = $config?->get('language');
if (is_string($languageDirectory) && is_dir($languageDirectory)) {
- $poMoAdapter->registerDirectory(
- $languageDirectory,
- TranslatorInterface::DEFAULT_DOMAIN
- );
- $jsonAdapter->registerDirectory(
- $languageDirectory,
- TranslatorInterface::DEFAULT_DOMAIN
- );
+ $translator->registerDirectory(TranslatorInterface::DEFAULT_DOMAIN, $languageDirectory);
}
}
$manager?->dispatch('kernel.registerProviders', $this);
diff --git a/src/HttpKernel/Helper/AbstractHelper.php b/src/HttpKernel/Helper/AbstractHelper.php
index d2119ec..05cd58e 100644
--- a/src/HttpKernel/Helper/AbstractHelper.php
+++ b/src/HttpKernel/Helper/AbstractHelper.php
@@ -3,7 +3,7 @@
namespace ArrayAccess\TrayDigita\HttpKernel\Helper;
-use ArrayAccess\TrayDigita\Cache\Cache;
+use ArrayAccess\TrayDigita\Cache\Entities;
use ArrayAccess\TrayDigita\Container\Interfaces\ContainerAllocatorInterface;
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface;
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface;
diff --git a/src/HttpKernel/Helper/KernelControllerLoader.php b/src/HttpKernel/Helper/KernelControllerLoader.php
index 2711809..1fd44e6 100644
--- a/src/HttpKernel/Helper/KernelControllerLoader.php
+++ b/src/HttpKernel/Helper/KernelControllerLoader.php
@@ -14,6 +14,8 @@
use function class_exists;
use function is_bool;
use function is_dir;
+use function trim;
+use function ucwords;
class KernelControllerLoader extends AbstractLoaderNameBased
{
@@ -21,19 +23,58 @@ protected function getNameSpace(): ?string
{
return $this->kernel->getControllerNameSpace();
}
+ protected function doRegister(): bool
+ {
+ if (!$this->isProcessable()) {
+ return false;
+ }
+
+ // preprocess
+ $this->preProcess();
+ $mode = ucwords(trim($this->getMode()));
+ $manager = $this->getManager();
+ $mode && $manager?->dispatch(
+ "kernel.beforeRegisterControllers",
+ $this->kernel
+ );
+ try {
+ $maxDepth = 20;
+ foreach ($this->kernel->getControllersDirectories() as $dir) {
+ if (!is_dir($dir)) {
+ continue;
+ }
+ foreach ($this
+ ->createFinder(
+ $dir,
+ "<= $maxDepth",
+ '/^[_A-za-z]([a-zA-Z0-9]+)?\.php$/'
+ )
+ ->files() as $file) {
+ $this->loadService($file);
+ }
+ }
+ $mode && $manager?->dispatch(
+ "kernel.registerControllers",
+ $this->kernel
+ );
+ } finally {
+ $mode && $manager?->dispatch(
+ "kernel.afterRegisterControllers",
+ $this->kernel
+ );
+
+ // postprocess
+ $this->postProcess();
+ }
+ return true;
+ }
/**
* @return ?Finder
*/
protected function getFileLists(): ?Finder
{
- $maxDepth = 20;
- $directory = $this->getDirectory();
- return !$directory || ! is_dir($directory)
- ? null
- : $this
- ->createFinder($this->getDirectory(), "<= $maxDepth", '/^[_A-za-z]([a-zA-Z0-9]+)?\.php$/')
- ->files();
+ return null;
}
/**
@@ -44,15 +85,6 @@ public function getRouter() : RouterInterface
return $this->kernel->getHttpKernel()->getRouter();
}
- protected function getDirectory(): ?string
- {
- $namespace = $this->getNameSpace();
- $directory = $namespace
- ? $this->kernel->getRegisteredDirectories()[$namespace]??null
- : null;
- return $directory && is_dir($directory) ? $directory : null;
- }
-
protected function getContainer(): ContainerInterface
{
return parent::getContainer()??Decorator::container();
@@ -66,8 +98,7 @@ protected function getMode(): string
protected function isProcessable(): bool
{
$processable = $this->getNameSpace()
- && $this->getDirectory()
- && !$this->kernel->getConfigError();
+ && ! $this->kernel->getConfigError();
if ($processable) {
$canBeProcess = $this
->getManager()
diff --git a/src/Kernel/Decorator.php b/src/Kernel/Decorator.php
index 2eaccdd..1739185 100644
--- a/src/Kernel/Decorator.php
+++ b/src/Kernel/Decorator.php
@@ -267,6 +267,10 @@ public static function service(string $name)
return self::resolveDepend($name);
}
+ /**
+ * @param string $name
+ * @return SystemContainerInterface
+ */
private static function resolveInternal(string $name): SystemContainerInterface
{
$container = self::container();
diff --git a/src/Kernel/Interfaces/KernelInterface.php b/src/Kernel/Interfaces/KernelInterface.php
index 67411ad..0244d95 100644
--- a/src/Kernel/Interfaces/KernelInterface.php
+++ b/src/Kernel/Interfaces/KernelInterface.php
@@ -20,6 +20,10 @@ interface KernelInterface extends RunnableInterface, TerminableInterface
*/
const BASE_CONFIG_FILE_NAME = 'config.php';
+ public function registerControllerDirectory(string ...$directory);
+
+ public function removeControllerDirectory(string ...$directory);
+
public function getStartMemory() : int;
public function getStartTime() : float;
diff --git a/src/L10n/Translations/AbstractAdapter.php b/src/L10n/Translations/AbstractAdapter.php
index 62ae96f..4f178f5 100644
--- a/src/L10n/Translations/AbstractAdapter.php
+++ b/src/L10n/Translations/AbstractAdapter.php
@@ -4,9 +4,19 @@
namespace ArrayAccess\TrayDigita\L10n\Translations;
use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\AdapterInterface;
+use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\TranslatorInterface;
abstract class AbstractAdapter implements AdapterInterface
{
+ public function __construct(protected TranslatorInterface $translator)
+ {
+ }
+
+ public function getTranslator(): TranslatorInterface
+ {
+ return $this->translator;
+ }
+
public function getName(): string
{
return $this::class;
diff --git a/src/L10n/Translations/Adapter/Database/DatabaseAdapter.php b/src/L10n/Translations/Adapter/Database/DatabaseAdapter.php
index a3a8b59..879ee1a 100644
--- a/src/L10n/Translations/Adapter/Database/DatabaseAdapter.php
+++ b/src/L10n/Translations/Adapter/Database/DatabaseAdapter.php
@@ -39,8 +39,11 @@ class DatabaseAdapter extends AbstractAdapter
private PluralForm $pluralForm;
- public function __construct(Connection|DoctrineConnection $connection)
- {
+ public function __construct(
+ TranslatorInterface $translator,
+ Connection|DoctrineConnection $connection
+ ) {
+ parent::__construct($translator);
$this->connection = $connection;
$this->pluralForm = new PluralForm(
2,
@@ -58,11 +61,6 @@ public function getTableName(): string
return $this->tableName;
}
- public function setTableName(string $tableName): void
- {
- $this->tableName = $tableName;
- }
-
/**
* @param string $language
* @param string $domain
diff --git a/src/L10n/Translations/Adapter/Gettext/PoMoAdapter.php b/src/L10n/Translations/Adapter/Gettext/PoMoAdapter.php
index 7b14640..f858bc7 100644
--- a/src/L10n/Translations/Adapter/Gettext/PoMoAdapter.php
+++ b/src/L10n/Translations/Adapter/Gettext/PoMoAdapter.php
@@ -1,5 +1,4 @@
>
@@ -48,8 +46,9 @@ class PoMoAdapter extends AbstractAdapter implements AdapterBasedFileInterface
*/
private array $translations = [];
- public function __construct()
+ public function __construct(TranslatorInterface $translator)
{
+ parent::__construct($translator);
$this->reader = new GettextReader(new TranslationFactory());
}
@@ -166,38 +165,21 @@ public function addFromFile(
);
}
- /**
- *
- * @param string $directory
- * @param string $domain
- * @param bool $strict
- * @return bool
- */
- public function registerDirectory(string $directory, string $domain, bool $strict = false) : bool
- {
- if (!is_dir($directory)) {
- return false;
- }
- $directory = realpath($directory)?:DataNormalizer::normalizeDirectorySeparator($directory, true);
- $this->registeredDirectory[$domain][$directory] ??= false;
- $this->strict[$domain][$directory] = $strict;
- return true;
- }
-
private function scanLanguage(string $domain, string $language) : ?Entries
{
- $entries = $this->translations[$domain][$language]??null;
- if (!isset($this->registeredDirectory[$domain])) {
- return $entries;
- }
-
+ $entries = $this->translations[$domain][$language]??null;
+ $directories = $this->translator->getRegisteredDirectories()[$domain]??[];
$translations = null;
- foreach ($this->registeredDirectory[$domain] as $directory => $status) {
- if ($status === true) {
+ foreach ($directories as $directory) {
+ if (!is_string($directory) || !is_dir($directory)) {
+ continue;
+ }
+ $directory = realpath($directory);
+ if (!empty($this->registeredDirectory[$domain][$directory][$language])) {
continue;
}
- $this->registeredDirectory[$domain][$directory] = true;
+ $this->registeredDirectory[$domain][$directory][$language] = true;
$file = "$directory/$domain-$language.mo";
$file = !is_file($file) || !is_readable($file)
? "$directory/$domain-$language.po"
diff --git a/src/L10n/Translations/Adapter/Json/JsonAdapter.php b/src/L10n/Translations/Adapter/Json/JsonAdapter.php
index 70fcbd4..d77c386 100644
--- a/src/L10n/Translations/Adapter/Json/JsonAdapter.php
+++ b/src/L10n/Translations/Adapter/Json/JsonAdapter.php
@@ -8,18 +8,17 @@
use ArrayAccess\TrayDigita\L10n\Translations\AbstractAdapter;
use ArrayAccess\TrayDigita\L10n\Translations\Entries;
use ArrayAccess\TrayDigita\L10n\Translations\Exceptions\UnsupportedLanguageException;
-use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\AdapterBasedFileInterface;
use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\EntryInterface;
use ArrayAccess\TrayDigita\L10n\Translations\Interfaces\TranslatorInterface;
-use ArrayAccess\TrayDigita\Util\Filter\DataNormalizer;
use Throwable;
use function is_dir;
use function is_file;
use function is_readable;
+use function is_string;
use function realpath;
use function sprintf;
-class JsonAdapter extends AbstractAdapter implements AdapterBasedFileInterface
+class JsonAdapter extends AbstractAdapter
{
/**
* @var array>
@@ -33,24 +32,6 @@ class JsonAdapter extends AbstractAdapter implements AdapterBasedFileInterface
private array $translations = [];
- /**
- *
- * @param string $directory
- * @param string $domain
- * @param bool $strict
- * @return bool
- */
- public function registerDirectory(string $directory, string $domain, bool $strict = false) : bool
- {
- if (!is_dir($directory)) {
- return false;
- }
- $directory = realpath($directory)?:DataNormalizer::normalizeDirectorySeparator($directory, true);
- $this->registeredDirectory[$domain][$directory] ??= false;
- $this->strict[$domain][$directory] = $strict;
- return true;
- }
-
public function getName(): string
{
return 'Json';
@@ -72,16 +53,17 @@ public function getTranslationLanguage(
private function scanLanguage(string $domain, string $language) : ?Entries
{
- $entries = $this->translations[$domain][$language]??null;
- if (!isset($this->registeredDirectory[$domain])) {
- return $entries;
- }
-
- foreach ($this->registeredDirectory[$domain] as $directory => $status) {
- if ($status === true) {
+ $entries = $this->translations[$domain][$language]??null;
+ $directories = $this->translator->getRegisteredDirectories()[$domain]??[];
+ foreach ($directories as $directory) {
+ if (!is_string($directory) || !is_dir($directory)) {
+ continue;
+ }
+ $directory = realpath($directory);
+ if (!empty($this->registeredDirectory[$domain][$directory][$language])) {
continue;
}
- $this->registeredDirectory[$domain][$directory] = true;
+ $this->registeredDirectory[$domain][$directory][$language] = true;
$file = "$directory/$domain-$language.json";
$file = !is_file($file) || !is_readable($file)
? "$directory/$domain-$language.json"
diff --git a/src/L10n/Translations/Entities/Translation.php b/src/L10n/Translations/Entities/Translation.php
new file mode 100644
index 0000000..e8f8d3e
--- /dev/null
+++ b/src/L10n/Translations/Entities/Translation.php
@@ -0,0 +1,209 @@
+ 'utf8mb4', // remove this or change to utf8 if not use mysql
+ 'collation' => 'utf8mb4_unicode_ci', // remove this if not use mysql
+ 'comment' => 'Table translations',
+ ]
+)]
+#[UniqueConstraint(
+ name: 'unique_language_domain_original',
+ columns: [
+ 'language',
+ 'domain',
+ 'original'
+ ]
+)]
+#[Index(
+ columns: ['language'],
+ name: 'index_language'
+)]
+#[Index(
+ columns: ['domain'],
+ name: 'index_domain'
+)]
+#[HasLifecycleCallbacks]
+class Translation extends AbstractEntity
+{
+ const TABLE_NAME = 'translations';
+
+ #[Id]
+ #[GeneratedValue('AUTO')]
+ #[Column(
+ name: 'id',
+ type: Types::BIGINT,
+ length: 20,
+ updatable: false,
+ options: [
+ 'unsigned' => true,
+ 'comment' => 'Primary key id'
+ ]
+ )]
+ protected int $id;
+
+ #[Column(
+ name: 'language',
+ type: Types::STRING,
+ length: 10,
+ nullable: false,
+ options: [
+ 'comment' => 'Language'
+ ]
+ )]
+ protected string $language;
+
+ #[Column(
+ name: 'domain',
+ type: Types::STRING,
+ length: 128,
+ nullable: false,
+ options: [
+ 'default' => TranslatorInterface::DEFAULT_DOMAIN,
+ 'comment' => 'Text domain'
+ ]
+ )]
+ protected string $domain = TranslatorInterface::DEFAULT_DOMAIN;
+
+ #[Column(
+ name: 'original',
+ type: Types::STRING,
+ length: 1024,
+ nullable: false,
+ options: [
+ 'comment' => 'Original text'
+ ]
+ )]
+ protected string $original;
+
+ #[Column(
+ name: 'translation',
+ type: Types::STRING,
+ length: 1024,
+ nullable: true,
+ options: [
+ 'comment' => 'Translated text'
+ ]
+ )]
+ protected string $translation;
+
+ #[Column(
+ name: 'plural_translation',
+ type: Types::STRING,
+ length: 1024,
+ nullable: true,
+ options: [
+ 'default' => null,
+ 'comment' => 'Plural Translated text'
+ ]
+ )]
+ protected ?string $plural_translation;
+
+ #[Column(
+ name: 'context',
+ type: Types::STRING,
+ length: 2048,
+ nullable: true,
+ options: [
+ 'default' => null,
+ 'comment' => 'Context for the translator'
+ ]
+ )]
+ protected ?string $context;
+
+ public function __construct()
+ {
+ $this->domain = TranslatorInterface::DEFAULT_DOMAIN;
+ }
+
+ public function getId() : int
+ {
+ return $this->id;
+ }
+
+ public function getLanguage(): string
+ {
+ return $this->language;
+ }
+
+ public function setLanguage(string $language): void
+ {
+ $this->language = $language;
+ }
+
+ public function getDomain(): string
+ {
+ return $this->domain;
+ }
+
+ public function setDomain(string $domain): void
+ {
+ $this->domain = $domain;
+ }
+
+ public function getOriginal(): string
+ {
+ return $this->original;
+ }
+
+ public function setOriginal(string $original): void
+ {
+ $this->original = $original;
+ }
+
+ public function getTranslation(): string
+ {
+ return $this->translation;
+ }
+
+ public function setTranslation(string $translation): void
+ {
+ $this->translation = $translation;
+ }
+
+ public function getPluralTranslation(): ?string
+ {
+ return $this->plural_translation;
+ }
+
+ public function setPluralTranslation(?string $plural_translation): void
+ {
+ $this->plural_translation = $plural_translation;
+ }
+
+ public function getContext(): ?string
+ {
+ return $this->context;
+ }
+
+ public function setContext(?string $context): void
+ {
+ $this->context = $context;
+ }
+}
diff --git a/src/L10n/Translations/Interfaces/AdapterBasedFileInterface.php b/src/L10n/Translations/Interfaces/AdapterBasedFileInterface.php
deleted file mode 100644
index 479cd64..0000000
--- a/src/L10n/Translations/Interfaces/AdapterBasedFileInterface.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
+ */
+ public function getRegisteredDirectories() : array;
+
/**
* @param AdapterInterface $adapter
* @param string|null $name
diff --git a/src/L10n/Translations/Translator.php b/src/L10n/Translations/Translator.php
index 834a4c3..0fc6eba 100644
--- a/src/L10n/Translations/Translator.php
+++ b/src/L10n/Translations/Translator.php
@@ -13,10 +13,14 @@
use ArrayAccess\TrayDigita\Traits\Manager\ManagerAllocatorTrait;
use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait;
use Throwable;
+use function array_unique;
+use function array_values;
use function is_array;
+use function is_dir;
use function is_float;
use function is_int;
use function is_object;
+use function realpath;
use function round;
use function spl_object_hash;
use function sprintf;
@@ -46,6 +50,16 @@ class Translator implements TranslatorInterface, ManagerAllocatorInterface
*/
private array $adapterHash = [];
+ /**
+ * @var array
+ */
+ private array $registeredDirectories = [];
+
+ /**
+ * @var array
+ */
+ private array $registeredAdapterDirectoryRegistered = [];
+
/**
* @param string $language
* @param string $systemLanguage
@@ -112,6 +126,30 @@ public function setLanguage(string $language) : void
$this->language = $language;
}
+ public function registerDirectory(string $domain, string ...$directory): bool
+ {
+ $this->registeredDirectories[$domain] ??= [];
+ $succeed = false;
+ foreach ($directory as $dir) {
+ if (!is_dir($dir)) {
+ continue;
+ }
+ $dir = realpath($dir);
+ $this->registeredDirectories[$domain][] = $dir;
+ $succeed = true;
+ }
+ $this->registeredDirectories[$domain] = array_values(array_unique($this->registeredDirectories[$domain]));
+ return $succeed;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRegisteredDirectories(): array
+ {
+ return $this->registeredDirectories;
+ }
+
public function setAdapter(string $name, AdapterInterface $adapter) : void
{
// ::class;
diff --git a/src/Lang/default.pot b/src/Lang/default.pot
index d1bfe41..17b23f3 100644
--- a/src/Lang/default.pot
+++ b/src/Lang/default.pot
@@ -2,7 +2,7 @@
msgid ""
msgstr ""
"Project-Id-Version: TrayDigita 1.0.0\n"
-"POT-Creation-Date: 2023-10-22 12:20+0700\n"
+"POT-Creation-Date: 2023-10-24 06:36+0700\n"
"PO-Revision-Date: 2023-09-24 19:00+0700\n"
"Last-Translator: ArrayAccess\n"
"Language-Team: ArrayAccess\n"
@@ -206,7 +206,7 @@ msgstr ""
#: Console/Command/ApplicationChecker/CacheChecker.php:58
#: Console/Command/ApplicationChecker/CacheChecker.php:94
-#: Console/Command/DatabaseChecker.php:1588
+#: Console/Command/DatabaseChecker.php:1600
msgctxt "console"
msgid "Error:"
msgstr ""
@@ -636,8 +636,8 @@ msgstr ""
#: Console/Command/ChecksumGenerator.php:134
#: Console/Command/CommandGenerator.php:333
#: Console/Command/ControllerGenerator.php:296
-#: Console/Command/DatabaseChecker.php:1255
-#: Console/Command/DatabaseChecker.php:1397
+#: Console/Command/DatabaseChecker.php:1267
+#: Console/Command/DatabaseChecker.php:1409
#: Console/Command/DatabaseEventGenerator.php:330
#: Console/Command/EntityGenerator.php:327
#: Console/Command/MiddlewareGenerator.php:327
@@ -650,8 +650,8 @@ msgstr ""
#: Console/Command/ChecksumGenerator.php:149
#: Console/Command/CommandGenerator.php:346
#: Console/Command/ControllerGenerator.php:309
-#: Console/Command/DatabaseChecker.php:1270
-#: Console/Command/DatabaseChecker.php:1412
+#: Console/Command/DatabaseChecker.php:1282
+#: Console/Command/DatabaseChecker.php:1424
#: Console/Command/DatabaseEventGenerator.php:343
#: Console/Command/EntityGenerator.php:340
#: Console/Command/MiddlewareGenerator.php:342
@@ -664,8 +664,8 @@ msgstr ""
#: Console/Command/ChecksumGenerator.php:161
#: Console/Command/CommandGenerator.php:390
#: Console/Command/ControllerGenerator.php:351
-#: Console/Command/DatabaseChecker.php:1282
-#: Console/Command/DatabaseChecker.php:1426
+#: Console/Command/DatabaseChecker.php:1294
+#: Console/Command/DatabaseChecker.php:1438
#: Console/Command/DatabaseEventGenerator.php:387
#: Console/Command/EntityGenerator.php:385
#: Console/Command/MiddlewareGenerator.php:386
@@ -874,29 +874,29 @@ msgctxt "console"
msgid "%s Controller successfully created!"
msgstr ""
-#: Console/Command/DatabaseChecker.php:85
+#: Console/Command/DatabaseChecker.php:95
msgctxt "console"
msgid "Check & validate database."
msgstr ""
-#: Console/Command/DatabaseChecker.php:93
+#: Console/Command/DatabaseChecker.php:103
msgctxt "console"
msgid "Validate changed / difference about database schema"
msgstr ""
-#: Console/Command/DatabaseChecker.php:104
+#: Console/Command/DatabaseChecker.php:114
#, php-format
msgctxt "console"
msgid "Print sql query create schema (should execute with %s command)"
msgstr ""
-#: Console/Command/DatabaseChecker.php:120
+#: Console/Command/DatabaseChecker.php:130
#, php-format
msgctxt "console"
msgid "Dump create schema (should execute with %s & %s command)"
msgstr ""
-#: Console/Command/DatabaseChecker.php:140
+#: Console/Command/DatabaseChecker.php:150
#, php-format
msgctxt "console"
msgid ""
@@ -904,13 +904,13 @@ msgid ""
"command)"
msgstr ""
-#: Console/Command/DatabaseChecker.php:156
+#: Console/Command/DatabaseChecker.php:166
#, php-format
msgctxt "console"
msgid "Optimize database when possible (should execute with %s command)"
msgstr ""
-#: Console/Command/DatabaseChecker.php:170
+#: Console/Command/DatabaseChecker.php:180
#, php-format
msgctxt "console"
msgid ""
@@ -919,284 +919,284 @@ msgid ""
"services."
msgstr ""
-#: Console/Command/DatabaseChecker.php:248
-#: Console/Command/DatabaseChecker.php:1545
+#: Console/Command/DatabaseChecker.php:258
+#: Console/Command/DatabaseChecker.php:1557
msgctxt "console"
msgid "Can not get database object from container"
msgstr ""
-#: Console/Command/DatabaseChecker.php:259
-#: Console/Command/DatabaseChecker.php:1557
+#: Console/Command/DatabaseChecker.php:269
+#: Console/Command/DatabaseChecker.php:1569
msgctxt "console"
msgid "Database connection is not valid object from container"
msgstr ""
-#: Console/Command/DatabaseChecker.php:364
+#: Console/Command/DatabaseChecker.php:374
msgctxt "console"
msgid "No schema can be created"
msgstr ""
-#: Console/Command/DatabaseChecker.php:396
-#: Console/Command/DatabaseChecker.php:1360
-#: Console/Command/DatabaseChecker.php:1371
+#: Console/Command/DatabaseChecker.php:407
+#: Console/Command/DatabaseChecker.php:1372
+#: Console/Command/DatabaseChecker.php:1383
msgctxt "console"
msgid "No change into database schema"
msgstr ""
-#: Console/Command/DatabaseChecker.php:426
-#: Console/Command/DatabaseChecker.php:439
+#: Console/Command/DatabaseChecker.php:437
+#: Console/Command/DatabaseChecker.php:450
#, php-format
msgctxt "console"
msgid "Table \"%s\" does not exist!"
msgstr ""
-#: Console/Command/DatabaseChecker.php:464
+#: Console/Command/DatabaseChecker.php:476
#, php-format
msgctxt "console"
msgid "Table \"%s\" %s"
msgstr ""
-#: Console/Command/DatabaseChecker.php:475
+#: Console/Command/DatabaseChecker.php:487
msgctxt "console"
msgid "no difference"
msgstr ""
-#: Console/Command/DatabaseChecker.php:480
-#: Console/Command/DatabaseChecker.php:505
-#: Console/Command/DatabaseChecker.php:1918
+#: Console/Command/DatabaseChecker.php:492
+#: Console/Command/DatabaseChecker.php:517
+#: Console/Command/DatabaseChecker.php:1931
msgctxt "console"
msgid "NEED TO OPTIMIZE"
msgstr ""
-#: Console/Command/DatabaseChecker.php:497
+#: Console/Command/DatabaseChecker.php:509
#, php-format
msgctxt "console"
msgid "Table \"%s\" need to be change"
msgstr ""
-#: Console/Command/DatabaseChecker.php:534
-#: Console/Command/DatabaseChecker.php:556
-#: Console/Command/DatabaseChecker.php:578
-#: Console/Command/DatabaseChecker.php:600
-#: Console/Command/DatabaseChecker.php:622
-#: Console/Command/DatabaseChecker.php:644
-#: Console/Command/DatabaseChecker.php:666
-#: Console/Command/DatabaseChecker.php:688
-#: Console/Command/DatabaseChecker.php:713
-#: Console/Command/DatabaseChecker.php:735
+#: Console/Command/DatabaseChecker.php:546
+#: Console/Command/DatabaseChecker.php:568
+#: Console/Command/DatabaseChecker.php:590
+#: Console/Command/DatabaseChecker.php:612
+#: Console/Command/DatabaseChecker.php:634
+#: Console/Command/DatabaseChecker.php:656
+#: Console/Command/DatabaseChecker.php:678
+#: Console/Command/DatabaseChecker.php:700
+#: Console/Command/DatabaseChecker.php:725
+#: Console/Command/DatabaseChecker.php:747
msgctxt "console"
msgid "Column"
msgstr ""
-#: Console/Command/DatabaseChecker.php:536
+#: Console/Command/DatabaseChecker.php:548
msgctxt "console"
msgid "type"
msgstr ""
-#: Console/Command/DatabaseChecker.php:538
-#: Console/Command/DatabaseChecker.php:560
-#: Console/Command/DatabaseChecker.php:582
-#: Console/Command/DatabaseChecker.php:604
-#: Console/Command/DatabaseChecker.php:626
-#: Console/Command/DatabaseChecker.php:648
-#: Console/Command/DatabaseChecker.php:670
-#: Console/Command/DatabaseChecker.php:693
-#: Console/Command/DatabaseChecker.php:717
-#: Console/Command/DatabaseChecker.php:739
+#: Console/Command/DatabaseChecker.php:550
+#: Console/Command/DatabaseChecker.php:572
+#: Console/Command/DatabaseChecker.php:594
+#: Console/Command/DatabaseChecker.php:616
+#: Console/Command/DatabaseChecker.php:638
+#: Console/Command/DatabaseChecker.php:660
+#: Console/Command/DatabaseChecker.php:682
+#: Console/Command/DatabaseChecker.php:705
+#: Console/Command/DatabaseChecker.php:729
+#: Console/Command/DatabaseChecker.php:751
#, php-format
msgctxt "console"
msgid "change from: [%s] to [%s]"
msgstr ""
-#: Console/Command/DatabaseChecker.php:558
+#: Console/Command/DatabaseChecker.php:570
msgctxt "console"
msgid "default value"
msgstr ""
-#: Console/Command/DatabaseChecker.php:690
+#: Console/Command/DatabaseChecker.php:702
msgctxt "console"
msgid "scale"
msgstr ""
-#: Console/Command/DatabaseChecker.php:737
+#: Console/Command/DatabaseChecker.php:749
msgctxt "console"
msgid "comment"
msgstr ""
-#: Console/Command/DatabaseChecker.php:760
+#: Console/Command/DatabaseChecker.php:772
msgctxt "console"
msgid "Added Column"
msgstr ""
-#: Console/Command/DatabaseChecker.php:771
+#: Console/Command/DatabaseChecker.php:783
msgctxt "console"
msgid "Removed Column"
msgstr ""
-#: Console/Command/DatabaseChecker.php:782
+#: Console/Command/DatabaseChecker.php:794
msgctxt "console"
msgid "Renamed Column"
msgstr ""
-#: Console/Command/DatabaseChecker.php:784
+#: Console/Command/DatabaseChecker.php:796
#, php-format
msgctxt "console"
msgid "from [%s] to [%s]"
msgstr ""
-#: Console/Command/DatabaseChecker.php:807
+#: Console/Command/DatabaseChecker.php:819
msgctxt "console"
msgid "Modify Index"
msgstr ""
-#: Console/Command/DatabaseChecker.php:809
+#: Console/Command/DatabaseChecker.php:821
#, php-format
msgctxt "console"
msgid "from %s to %s"
msgstr ""
-#: Console/Command/DatabaseChecker.php:842
+#: Console/Command/DatabaseChecker.php:854
msgctxt "console"
msgid "Added Index"
msgstr ""
-#: Console/Command/DatabaseChecker.php:860
+#: Console/Command/DatabaseChecker.php:872
msgctxt "console"
msgid "Removed Index"
msgstr ""
-#: Console/Command/DatabaseChecker.php:880
+#: Console/Command/DatabaseChecker.php:892
msgctxt "console"
msgid "Renamed Index"
msgstr ""
-#: Console/Command/DatabaseChecker.php:901
+#: Console/Command/DatabaseChecker.php:913
msgctxt "console"
msgid "Modify ForeignKey"
msgstr ""
-#: Console/Command/DatabaseChecker.php:947
+#: Console/Command/DatabaseChecker.php:959
msgctxt "console"
msgid "Added ForeignKey"
msgstr ""
-#: Console/Command/DatabaseChecker.php:969
+#: Console/Command/DatabaseChecker.php:981
msgctxt "console"
msgid "Removed ForeignKey"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1006
-#: Console/Command/DatabaseChecker.php:1039
-#: Console/Command/DatabaseChecker.php:1073
+#: Console/Command/DatabaseChecker.php:1018
+#: Console/Command/DatabaseChecker.php:1051
+#: Console/Command/DatabaseChecker.php:1085
#, php-format
msgctxt "console"
msgid "Table \"%s\" for %s & does not exist in schema"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1101
+#: Console/Command/DatabaseChecker.php:1113
#, php-format
msgctxt "console"
msgid "Table \"%s\" for %s"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1148
+#: Console/Command/DatabaseChecker.php:1160
msgctxt "console"
msgid "Contains changed database schema, you can execute command :"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1173
-#: Console/Command/DatabaseChecker.php:1971
+#: Console/Command/DatabaseChecker.php:1185
+#: Console/Command/DatabaseChecker.php:1984
msgctxt "console"
msgid ""
"Contains database table that can be optimized, you can execute command :"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1201
+#: Console/Command/DatabaseChecker.php:1213
msgctxt "console"
msgid "There are no tables that can be optimized"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1216
+#: Console/Command/DatabaseChecker.php:1228
#, php-format
msgctxt "console"
msgid "%s does not yet support optimization"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1237
+#: Console/Command/DatabaseChecker.php:1249
#, php-format
msgctxt "console"
msgid "Table \"%s\" can be freed up to : (%s)"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1291
-#: Console/Command/DatabaseChecker.php:1439
+#: Console/Command/DatabaseChecker.php:1303
+#: Console/Command/DatabaseChecker.php:1451
msgctxt "console"
msgid "PLEASE DO NOT CANCEL OPERATION!"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1299
+#: Console/Command/DatabaseChecker.php:1311
msgctxt "console"
msgid "Optimizing table"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1328
+#: Console/Command/DatabaseChecker.php:1340
msgctxt "console"
msgid "FAIL"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1338
-#: Console/Command/DatabaseChecker.php:1527
+#: Console/Command/DatabaseChecker.php:1350
+#: Console/Command/DatabaseChecker.php:1539
msgctxt "console"
msgid "ALL DONE!"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1381
+#: Console/Command/DatabaseChecker.php:1393
msgctxt "console"
msgid "Executed Command:"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1465
+#: Console/Command/DatabaseChecker.php:1477
msgctxt "console"
msgid "Executing:"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1483
-#: Console/Command/DatabaseChecker.php:1512
+#: Console/Command/DatabaseChecker.php:1495
+#: Console/Command/DatabaseChecker.php:1524
msgctxt "console"
msgid "Failed to execute command"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1582
+#: Console/Command/DatabaseChecker.php:1594
msgctxt "console"
msgid "Database connection error."
msgstr ""
-#: Console/Command/DatabaseChecker.php:1600
+#: Console/Command/DatabaseChecker.php:1612
msgctxt "console"
msgid "Database connection succeed"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1638
+#: Console/Command/DatabaseChecker.php:1650
msgctxt "console"
msgid "Database name"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1655
+#: Console/Command/DatabaseChecker.php:1667
msgctxt "console"
msgid "Database host"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1664
+#: Console/Command/DatabaseChecker.php:1676
msgctxt "console"
msgid "Database user"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1673
+#: Console/Command/DatabaseChecker.php:1685
msgctxt "console"
msgid "Database password"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1695
+#: Console/Command/DatabaseChecker.php:1707
#: Console/Command/SchedulerAction.php:120
#: Console/Command/SchedulerAction.php:204
#: Console/Command/SchedulerAction.php:215
@@ -1207,82 +1207,82 @@ msgctxt "console"
msgid "Unknown"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1704
+#: Console/Command/DatabaseChecker.php:1716
msgctxt "console"
msgid "Connection Object Info"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1712
+#: Console/Command/DatabaseChecker.php:1724
msgctxt "console"
msgid "Connection"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1766
+#: Console/Command/DatabaseChecker.php:1778
msgctxt "console"
msgid "ORM Configuration"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1772
+#: Console/Command/DatabaseChecker.php:1784
msgctxt "console"
msgid "Query Cache"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1773
+#: Console/Command/DatabaseChecker.php:1785
msgctxt "console"
msgid "Result Cache"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1774
+#: Console/Command/DatabaseChecker.php:1786
msgctxt "console"
msgid "Proxy Namespace"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1775
+#: Console/Command/DatabaseChecker.php:1787
msgctxt "console"
msgid "Proxy Directory"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1776
+#: Console/Command/DatabaseChecker.php:1788
msgctxt "console"
msgid "Metadata Driver"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1777
+#: Console/Command/DatabaseChecker.php:1789
msgctxt "console"
msgid "Repository Factory"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1778
+#: Console/Command/DatabaseChecker.php:1790
msgctxt "console"
msgid "Quote Strategy"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1779
+#: Console/Command/DatabaseChecker.php:1791
msgctxt "console"
msgid "Naming Factory"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1780
+#: Console/Command/DatabaseChecker.php:1792
msgctxt "console"
msgid "Schema Manager Factory"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1789
+#: Console/Command/DatabaseChecker.php:1801
msgctxt "console"
msgid "Not Set"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1799
+#: Console/Command/DatabaseChecker.php:1811
msgctxt "console"
msgid "Registered Schema / Entities"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1932
+#: Console/Command/DatabaseChecker.php:1945
msgctxt "console"
msgid "Changing"
msgstr ""
-#: Console/Command/DatabaseChecker.php:1946
+#: Console/Command/DatabaseChecker.php:1959
msgctxt "console"
msgid "Contains changed database schema, you can check with command :"
msgstr ""
@@ -1864,13 +1864,13 @@ msgctxt "console"
msgid "Time %s secs; Memory Usage: %s; Memory Peak Usage: %s"
msgstr ""
-#: Scheduler/Scheduler.php:168
+#: Scheduler/Scheduler.php:169
#, php-format
msgctxt "scheduler"
msgid "Callback task must be contain return type or instance of: %s"
msgstr ""
-#: Scheduler/Scheduler.php:445
+#: Scheduler/Scheduler.php:446
#, php-format
msgctxt "scheduler"
msgid "Scheduler is on progress with (%s) task remaining"
diff --git a/src/Logger/Entities/LogItem.php b/src/Logger/Entities/LogItem.php
new file mode 100644
index 0000000..2636961
--- /dev/null
+++ b/src/Logger/Entities/LogItem.php
@@ -0,0 +1,160 @@
+ 'utf8mb4', // remove this or change to utf8 if not use mysql
+ 'collation' => 'utf8mb4_unicode_ci', // remove this if not use mysql
+ 'comment' => 'Record for logs'
+ ]
+)]
+#[Index(
+ columns: ['channel', 'level'],
+ name: 'index_channel_level'
+)]
+#[HasLifecycleCallbacks]
+class LogItem extends AbstractEntity
+{
+ const TABLE_NAME = 'log_items';
+
+ #[Id]
+ #[GeneratedValue('AUTO')]
+ #[Column(
+ name: 'id',
+ type: Types::BIGINT,
+ length: 20,
+ updatable: false,
+ options: [
+ 'comment' => 'Primary key id'
+ ]
+ )]
+ protected int $id;
+
+ #[Column(
+ name: 'channel',
+ type: Types::STRING,
+ length: 255,
+ options: [
+ 'default' => 'default',
+ 'comment' => 'Log channel'
+ ]
+ )]
+ protected string $channel = 'default';
+
+ #[Column(
+ name: 'level',
+ type: Types::STRING,
+ length: 10,
+ options: [
+ 'comment' => 'Log level'
+ ]
+ )]
+ protected string $level;
+
+ #[Column(
+ name: 'message',
+ type: Types::BLOB,
+ length: AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB,
+ options: [
+ 'comment' => 'Log message records'
+ ]
+ )]
+ protected string $message;
+
+ #[Column(
+ name: 'created_time',
+ type: Types::INTEGER,
+ length: 10,
+ options: [
+ 'unsigned' => true,
+ 'comment' => 'Log time'
+ ]
+ )]
+ protected int $created_time;
+
+ public function __construct()
+ {
+ $this->created_time = time();
+ }
+
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ public function getChannel(): string
+ {
+ return $this->channel;
+ }
+
+ public function setChannel(string $channel): void
+ {
+ $this->channel = $channel;
+ }
+
+ public function getLevel(): string
+ {
+ return $this->level;
+ }
+
+ public function setLevel(string $level): void
+ {
+ $this->level = $level;
+ }
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+
+ public function setMessage(string $message): void
+ {
+ $this->message = $message;
+ }
+
+ public function getCreatedTime(): int
+ {
+ return $this->created_time;
+ }
+
+ public function getCreatedAt(): DateTimeInterface
+ {
+ return DateTimeImmutable::createFromFormat(
+ DateTimeInterface::RFC3339,
+ date(DateTimeInterface::RFC3339, $this->created_time)
+ );
+ }
+
+ public function setCreatedTime(int|DateTimeInterface $created_time): void
+ {
+ if ($created_time instanceof DateTimeInterface) {
+ $created_time = $created_time->getTimestamp();
+ }
+ $this->created_time = $created_time;
+ }
+}
diff --git a/src/Module/Traits/ModuleTrait.php b/src/Module/Traits/ModuleTrait.php
index 5796954..5d367bc 100644
--- a/src/Module/Traits/ModuleTrait.php
+++ b/src/Module/Traits/ModuleTrait.php
@@ -25,7 +25,7 @@ trait ModuleTrait
public function getKernel()
{
- return ContainerHelper::use(
+ return ContainerHelper::service(
KernelInterface::class,
$this->getContainer()
);
diff --git a/src/Routing/AbstractController.php b/src/Routing/AbstractController.php
index 89d335d..9c83022 100644
--- a/src/Routing/AbstractController.php
+++ b/src/Routing/AbstractController.php
@@ -8,6 +8,7 @@
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface;
use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface;
use ArrayAccess\TrayDigita\Http\Code;
+use ArrayAccess\TrayDigita\Http\Exceptions\HttpException;
use ArrayAccess\TrayDigita\Module\Interfaces\ModuleInterface;
use ArrayAccess\TrayDigita\Module\Modules;
use ArrayAccess\TrayDigita\Responder\Interfaces\FileResponderInterface;
@@ -265,20 +266,28 @@ public function dispatch(
} catch (Throwable) {
$_arguments = array_values($_arguments);
}
- if ($refMethod->isPrivate()) {
- $response = (function ($method, ...$arguments) {
- return $this->$method(...$arguments);
- })->call(
- $this,
- $resolver,
- $refMethod->getName(),
- ...$_arguments
- );
- } else {
- $response = $this->{$refMethod->getName()}(...$_arguments);
+ try {
+ if ($refMethod->isPrivate()) {
+ $response = (function ($method, ...$arguments) {
+ return $this->$method(...$arguments);
+ })->call(
+ $this,
+ $resolver,
+ $refMethod->getName(),
+ ...$_arguments
+ );
+ } else {
+ $response = $this->{$refMethod->getName()}(...$_arguments);
+ }
+ } catch (HttpException $e) {
+ $response = $e;
+ if (Code::statusMessage($e->getCode()) !== null) {
+ $this->statusCode = $e->getCode();
+ } else {
+ $this->statusCode = 500;
+ }
}
}
-
$statusCode = 200;
if (is_int($this->statusCode)
&& Code::statusMessage($this->statusCode) !== null
@@ -292,7 +301,10 @@ public function dispatch(
if (!$response instanceof ResponseInterface) {
$this->response ??= $this->getResponseFactory()->createResponse($statusCode);
- if (is_iterable($response) || $response instanceof JsonSerializable) {
+ if (is_iterable($response)
+ || $response instanceof JsonSerializable
+ || $this->asJSON && $response instanceof Throwable
+ ) {
$this->response = $this
->getJsonResponder()
->serve(
@@ -320,12 +332,11 @@ public function dispatch(
|| $response instanceof Stringable
|| is_object($response) && method_exists($response, '__tostring')
) {
- $response = (string) $response;
$useJson = false;
if ($this->asJSON) {
try {
json_decode(
- $response,
+ (string) $response,
flags: JSON_THROW_ON_ERROR
);
$useJson = true;
@@ -333,14 +344,16 @@ public function dispatch(
? $this->response->getBody()
: $this->getStreamFactory()->createStream();
$body->write($response);
- $this->response = $this->getJsonResponder()->appendContentType(
- $this->response->withBody($body)
- );
+ $this->response = $this->getJsonResponder()
+ ->appendContentType(
+ $this->response->withBody($body)
+ );
} catch (Throwable) {
}
}
if (!$useJson) {
- return $this->response = $this
+ $response = (string) $response;
+ $this->response = $this
->getHtmlResponder()
->serve($statusCode, $response, $this->response);
}
diff --git a/src/Scheduler/Entities/TaskScheduler.php b/src/Scheduler/Entities/TaskScheduler.php
new file mode 100644
index 0000000..6b7a817
--- /dev/null
+++ b/src/Scheduler/Entities/TaskScheduler.php
@@ -0,0 +1,263 @@
+ 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'comment' => 'Scheduler record list',
+ 'primaryKey' => [
+ 'identity'
+ ]
+ ]
+)]
+#[Index(
+ columns: ['name'],
+ name: 'index_name'
+)]
+#[Index(
+ columns: ['executed_object_class'],
+ name: 'index_executed_object_class'
+)]
+#[Index(
+ columns: ['status_code', 'execution_time', 'finish_time'],
+ name: 'index_status_code_execution_time_finish_time'
+)]
+#[HasLifecycleCallbacks]
+class TaskScheduler extends AbstractEntity
+{
+ const TABLE_NAME = 'task_schedulers';
+
+ #[Id]
+ #[Column(
+ name: 'identity',
+ type: Types::STRING,
+ length: 255,
+ options: [
+ 'primaryKey' => true,
+ 'comment' => 'Task identity'
+ ]
+ )]
+ protected string $identity;
+
+ #[Column(
+ name: 'name',
+ type: Types::STRING,
+ length: 255,
+ options: [
+ 'comment' => 'Task name'
+ ]
+ )]
+ protected string $name;
+
+ #[Column(
+ name: 'executed_object_class',
+ type: Types::STRING,
+ length: 512,
+ nullable: true,
+ options: [
+ 'default' => null,
+ 'comment' => 'Executed class name'
+ ]
+ )]
+ protected ?string $executed_object_class = null;
+
+ #[Column(
+ name: 'status_code',
+ type: Types::SMALLINT,
+ length: 3,
+ options: [
+ 'unsigned' => true,
+ 'default' => Runner::STATUS_QUEUE,
+ 'comment' => 'Task status code'
+ ]
+ )]
+ protected int $status_code = Runner::STATUS_QUEUE;
+
+ #[Column(
+ name: 'execution_time',
+ type: Types::INTEGER,
+ length: 10,
+ options: [
+ 'unsigned' => true,
+ 'default' => 0,
+ 'comment' => 'Task start executed'
+ ]
+ )]
+ protected int $execution_time = 0;
+
+ #[Column(
+ name: 'finish_time',
+ type: Types::INTEGER,
+ length: 10,
+ nullable: true,
+ options: [
+ 'unsigned' => true,
+ 'default' => null,
+ 'comment' => 'Cron interval'
+ ]
+ )]
+ protected ?int $finish_time = null;
+
+ #[Column(
+ name: 'execute_duration',
+ type: Types::FLOAT,
+ length: 10,
+ nullable: true,
+ options: [
+ 'unsigned' => true,
+ 'default' => null,
+ 'comment' => 'Execution duration'
+ ]
+ )]
+ protected ?float $execute_duration = null;
+
+ #[Column(
+ name: 'message',
+ type: TypeList::DATA_BLOB,
+ length: 4294967295,
+ nullable: true,
+ options: [
+ 'default' => null,
+ 'comment' => 'Option value data'
+ ]
+ )]
+ protected mixed $message = null;
+
+ public function __construct()
+ {
+ $this->execution_time = 0;
+ $this->finish_time = null;
+ $this->execute_duration = null;
+ $this->message = null;
+ }
+
+ public function getIdentity(): string
+ {
+ return $this->identity;
+ }
+
+ public function setIdentity(string $identity): void
+ {
+ $this->identity = $identity;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getExecutedObjectClass(): ?string
+ {
+ return $this->executed_object_class;
+ }
+
+ public function setExecutedObjectClass(?string $executed_object_class): void
+ {
+ $this->executed_object_class = $executed_object_class;
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->status_code;
+ }
+
+ public function setStatusCode(int $status_code): void
+ {
+ $this->status_code = $status_code;
+ }
+
+ public function getExecutionTime(): int
+ {
+ return $this->execution_time;
+ }
+
+ public function setExecutionTime(int $execution_time): void
+ {
+ $this->execution_time = $execution_time;
+ }
+
+ public function getFinishTime(): ?int
+ {
+ return $this->finish_time;
+ }
+
+ public function setFinishTime(?int $finish_time): void
+ {
+ $this->finish_time = $finish_time;
+ }
+
+ public function getExecuteDuration(): ?float
+ {
+ return $this->execute_duration;
+ }
+
+ public function setExecuteDuration(?float $execute_duration): void
+ {
+ $this->execute_duration = $execute_duration;
+ }
+
+ public function getMessage(): mixed
+ {
+ return $this->message;
+ }
+
+ public function setMessage(mixed $message): void
+ {
+ $this->message = $message;
+ }
+
+ public static function addEntityToMetadata(
+ Connection $connection
+ ): void {
+ if ($connection instanceof Connection) {
+ $connection = $connection->getEntityManager();
+ }
+ $metadataFactory = $connection->getMetadataFactory();
+ if ($metadataFactory->hasMetadataFor(__CLASS__)) {
+ return;
+ }
+ $configuration = $connection->getConfiguration();
+ $metadataFactory->setMetadataFor(
+ __CLASS__,
+ new ClassMetadata(
+ __CLASS__,
+ $configuration->getNamingStrategy(),
+ $configuration->getTypedFieldMapper()
+ )
+ );
+ }
+}
diff --git a/src/Scheduler/Loader/EntityLoader.php b/src/Scheduler/Loader/EntityLoader.php
new file mode 100644
index 0000000..e3fdaa3
--- /dev/null
+++ b/src/Scheduler/Loader/EntityLoader.php
@@ -0,0 +1,215 @@
+connection->getContainer());
+ $this->connection->registerEntityDirectory(
+ dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Entities'
+ );
+ }
+
+ private function checkEntityRegistration(): void
+ {
+ if ($this->checkedEntity) {
+ return;
+ }
+ $this->checkedEntity = true;
+ try {
+ $entity = $this->connection->getEntityManager();
+ $factory = $entity->getMetadataFactory();
+ if ($factory->hasMetadataFor(TaskScheduler::class)) {
+ return;
+ }
+ $metadata = $factory->getMetadataFor(TaskScheduler::class);
+ $schema = $this->connection->createSchemaManager();
+ if (!$schema->introspectTable($metadata->getTableName())) {
+ $table = (new SchemaTool($entity))
+ ->getSchemaFromMetadata([$metadata])
+ ->getTable($metadata->getTableName());
+ $schema->createTable($table);
+ }
+ } catch (Throwable) {
+ }
+ }
+
+ private function getRecordEntity(Task $task) : ?TaskScheduler
+ {
+ $this->checkEntityRegistration();
+
+ $id = $this->taskNameHash($task);
+ return $this
+ ->connection
+ ->find(
+ TaskScheduler::class,
+ $id
+ );
+ }
+
+ public function getRecord(Task $task): ?LastRecord
+ {
+ $id = $this->taskNameHash($task);
+ if (isset($this->lastRecords[$id])) {
+ return $this->lastRecords[$id];
+ }
+ $entity = $this->getRecordEntity($task);
+ if (!$entity) {
+ return null;
+ }
+
+ $lastExecutionTime = $entity->getExecutionTime();
+ $statusCode = $entity->getStatusCode();
+ $message = $entity->getMessage();
+ if ($message instanceof LastRecord) {
+ $messageLastExecutionTime = $message->getLastExecutionTime();
+ $messageStatusCode = $message->getStatusCode();
+ $message = $message->getMessage();
+ if (($statusCode === Runner::STATUS_UNKNOWN
+ || $statusCode === Runner::STATUS_QUEUE)
+ && (
+ $messageStatusCode !== Runner::STATUS_UNKNOWN
+ && $messageStatusCode !== Runner::STATUS_PROGRESS
+ )
+ ) {
+ $statusCode = $message->getStatusCode();
+ }
+ if ($messageLastExecutionTime > Runner::PREVIOUS_MIN_TIME
+ && ($lastExecutionTime === 0 || $lastExecutionTime)) {
+ $lastExecutionTime = $messageLastExecutionTime;
+ }
+ } elseif (!$message instanceof MessageInterface) {
+ if ($message !== null
+ && !is_string($message)
+ && !$message instanceof Stringable
+ ) {
+ $message = null;
+ }
+ $message = match ($entity->getStatusCode()) {
+ Runner::STATUS_SKIPPED => new Skipped($message),
+ Runner::STATUS_FAILURE => new Failure($message),
+ Runner::STATUS_EXITED => new Exited($message),
+ Runner::STATUS_PROGRESS => new Progress($message),
+ Runner::STATUS_STOPPED => new Stopped($message),
+ Runner::STATUS_SUCCESS => new Success($message),
+ default => new Unknown($message),
+ };
+ }
+
+ return $this->executionRecords[$id] = (new LastRecord(
+ $task,
+ $lastExecutionTime,
+ $message
+ ))->withStatusCode($statusCode);
+ }
+
+ private function saveRecord(LastRecord $record, Runner $runner, ?string $status = null): void
+ {
+ $isFinish = $status === self::FINISH;
+ switch ($status) {
+ case self::FINISH:
+ case self::PROGRESS:
+ case self::SKIPPED:
+ case self::EXITED:
+ $status = match ($status) {
+ self::PROGRESS => Runner::STATUS_PROGRESS,
+ self::SKIPPED => Runner::STATUS_SKIPPED,
+ self::EXITED => Runner::STATUS_EXITED,
+ default => $record->getStatusCode()
+ };
+ $task = $record->getTask();
+ $entity = $this->getRecordEntity($task);
+ if (!$entity) {
+ $entity = new TaskScheduler();
+ $entity->setName($task->getName());
+ $entity->setIdentity($this->taskNameHash($task));
+ }
+ if ($isFinish || $status === Runner::STATUS_EXITED) {
+ $entity->setExecuteDuration($runner->getExecutionDuration());
+ }
+
+ $entity->setFinishTime(
+ $isFinish ? time() : $entity->getFinishTime()
+ );
+ $entity->setExecutedObjectClass($task::class);
+ $entity->setExecutionTime($record->getLastExecutionTime());
+ $entity->setMessage($record->getMessage());
+ $entity->setStatusCode($status);
+ $em = $this->connection->getEntityManager();
+ $em->persist($entity);
+ $em->flush();
+ return;
+ }
+ }
+
+ public function storeExitRunner(Runner $runner, Scheduler $scheduler): ?LastRecord
+ {
+ $record = parent::storeExitRunner($runner, $scheduler);
+ if ($record) {
+ $this->saveRecord($record, $runner, self::EXITED);
+ }
+ return $record;
+ }
+
+ public function doSkipProgress(Runner $runner, Scheduler $scheduler): ?LastRecord
+ {
+ $record = parent::doSkipProgress($runner, $scheduler);
+ if ($record) {
+ $this->saveRecord($record, $runner, self::SKIPPED);
+ }
+ return $record;
+ }
+
+ public function doStartProgress(Runner $runner, Scheduler $scheduler): ?LastRecord
+ {
+ $record = parent::doStartProgress($runner, $scheduler);
+ if ($record) {
+ $this->saveRecord($record, $runner, self::PROGRESS);
+ }
+ return $record;
+ }
+
+ public function finish(int $executionTime, Runner $runner, Scheduler $scheduler): LastRecord
+ {
+ $record = parent::finish($executionTime, $runner, $scheduler);
+ if ($record) {
+ $this->saveRecord($record, $runner, self::FINISH);
+ }
+ return $record;
+ }
+
+ protected function doSaveRecords(bool $isFinish = false): void
+ {
+ }
+}
diff --git a/src/Scheduler/LocalRecordLoader.php b/src/Scheduler/Loader/LocalRecordLoader.php
similarity index 98%
rename from src/Scheduler/LocalRecordLoader.php
rename to src/Scheduler/Loader/LocalRecordLoader.php
index 0cf5c08..b177838 100644
--- a/src/Scheduler/LocalRecordLoader.php
+++ b/src/Scheduler/Loader/LocalRecordLoader.php
@@ -1,13 +1,14 @@
getContainer()
+ );
+ }
+
+ /**
+ * @return EntityManagerInterface
+ */
+ public function getEntityManager(): EntityManagerInterface
+ {
+ return $this->getConnection()->getEntityManager();
+ }
+}