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(); + } +}