diff --git a/src/Console/Command/ChecksumGenerator.php b/src/Console/Command/ChecksumGenerator.php index 3431102..26cf881 100644 --- a/src/Console/Command/ChecksumGenerator.php +++ b/src/Console/Command/ChecksumGenerator.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function basename; +use function clearstatcache; use function date; use function dirname; use function error_clear_last; @@ -250,6 +251,7 @@ static function ($e) { continue; } $hash = $printMode === 'sha1' ? $sha1Sum : $md5Sum; + clearstatcache(true, $realPath); $output->writeln( sprintf( '[%s] %s %s', diff --git a/src/Container/ContainerResolver.php b/src/Container/ContainerResolver.php index 74702e7..78e3966 100644 --- a/src/Container/ContainerResolver.php +++ b/src/Container/ContainerResolver.php @@ -47,11 +47,10 @@ public function getContainer(): ContainerInterface } /** - * @param callable|array|mixed $callable + * @template T of object + * @param callable|array|class-string|mixed $callable * @param array $arguments - * @return array|mixed - * @throws ContainerFrozenException - * @throws ContainerNotFoundException + * @return array|T|mixed * @throws Throwable */ public function resolveCallable(mixed $callable, array $arguments = []): mixed @@ -174,14 +173,16 @@ public function resolveArguments( ReflectionClass|ReflectionFunctionAbstract $reflection, $arguments ): array { - // resolver empty arguments when auto resolve enabled - if (!empty($arguments)) { - return $arguments; - } $reflectionName = $reflection->getName(); $reflection = $reflection instanceof ReflectionClass ? $reflection->getConstructor() : $reflection; + + // resolver empty arguments when auto resolve enabled + /*if (!empty($arguments) && count($arguments) === $reflection->getNumberOfRequiredParameters()) { + return $arguments; + }*/ + $parameters = $reflection?->getParameters()??[]; $container = $this->getContainer(); $containerParameters = method_exists($container, 'getParameters') @@ -191,7 +192,9 @@ public function resolveArguments( $containerAliases = method_exists($container, 'getAliases') ? $container->getAliases() : []; + $paramArguments = []; foreach ($parameters as $parameter) { + $argumentName = $parameter->getName(); $type = $parameter->getType(); if ($type instanceof ReflectionUnionType) { foreach ($type->getTypes() as $unionType) { @@ -214,28 +217,37 @@ public function resolveArguments( if (!$type instanceof ReflectionNamedType || $type->isBuiltin() ) { + if (isset($arguments[$parameter->getName()])) { + $paramArguments[$argumentName] = $containerParameters[$parameter->getName()]; + continue; + } if (array_key_exists($parameter->getName(), $containerParameters)) { - $arguments[] = $containerParameters[$parameter->getName()]; + $paramArguments[$argumentName] = $containerParameters[$parameter->getName()]; continue; } if ($parameter->isDefaultValueAvailable()) { - $arguments[] = $parameter->getDefaultValue(); + $paramArguments[$argumentName] = $parameter->getDefaultValue(); continue; } if ($parameter->allowsNull()) { - $arguments[] = null; + $paramArguments[$argumentName] = null; continue; } - $arguments = []; + $paramArguments = []; break; } + if (isset($arguments[$parameter->getName()])) { + $paramArguments[$argumentName] = $arguments[$parameter->getName()]; + continue; + } + $name = $type->getName(); if ($name === ContainerInterface::class || is_a($name, __CLASS__) ) { - $arguments[] = $container->has($name) + $paramArguments[$argumentName] = $container->has($name) ? $container->get($name) : $container; continue; @@ -244,7 +256,7 @@ public function resolveArguments( && isset($containerAliases[$name]) && $container->has($containerAliases[$name]) ) { - $arguments[] = $container->get($containerAliases[$name]); + $paramArguments[$argumentName] = $container->get($containerAliases[$name]); continue; } @@ -254,32 +266,32 @@ public function resolveArguments( if (is_string($param) && $container->has($param)) { $param = $container->get($param); } - $arguments[] = $param; + $paramArguments[$argumentName] = $param; continue; } if ($parameter->isDefaultValueAvailable()) { - $arguments[] = $parameter->getDefaultValue(); + $paramArguments[$argumentName] = $parameter->getDefaultValue(); continue; } if ($parameter->allowsNull()) { - $arguments[] = null; + $paramArguments[$argumentName] = null; continue; } - $arguments = []; + $paramArguments = []; break; } - $arguments[] = $container->get($name); + $paramArguments[$argumentName] = $container->get($name); } - if (($required = $reflection?->getNumberOfRequiredParameters()??0) > count($arguments)) { + if (($required = $reflection?->getNumberOfRequiredParameters()??0) > count($paramArguments)) { throw new UnResolveAbleException( sprintf( 'Could not resolve required arguments for : %s. Required %d argument, but %d given', $reflectionName, $required, - count($arguments) + count($paramArguments) ) ); } - return $arguments; + return $paramArguments; } } diff --git a/src/Database/Connection.php b/src/Database/Connection.php index 7cd2ce2..c878c98 100644 --- a/src/Database/Connection.php +++ b/src/Database/Connection.php @@ -7,6 +7,9 @@ use ArrayAccess\TrayDigita\Container\Interfaces\ContainerIndicateInterface; use ArrayAccess\TrayDigita\Database\Wrapper\DriverWrapper; use ArrayAccess\TrayDigita\Database\Wrapper\EntityManagerWrapper; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerAllocatorInterface; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; +use ArrayAccess\TrayDigita\Traits\Manager\ManagerAllocatorTrait; use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait; use ArrayAccess\TrayDigita\Util\Filter\Consolidation; use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; @@ -42,9 +45,10 @@ /** * @mixin DoctrineConnection */ -class Connection implements ContainerIndicateInterface +class Connection implements ContainerIndicateInterface, ManagerAllocatorInterface { - use ManagerDispatcherTrait; + use ManagerDispatcherTrait, + ManagerAllocatorTrait; private ?DoctrineConnection $connection = null; @@ -71,6 +75,10 @@ public function __construct( $this->config ??= ContainerHelper::use(Config::class, $this->container)??new Config(); $this->eventManager = $eventManager??new EventManager(); $this->defaultConfiguration = $configuration??new OrmConfiguration(); + $manager = ContainerHelper::getNull(ManagerInterface::class, $this->container); + if ($manager) { + $this->setManager($manager); + } } protected function getPrefixNameEventIdentity(): ?string diff --git a/src/Database/Wrapper/ConnectionWrapper.php b/src/Database/Wrapper/ConnectionWrapper.php index ea546f5..86e88f5 100644 --- a/src/Database/Wrapper/ConnectionWrapper.php +++ b/src/Database/Wrapper/ConnectionWrapper.php @@ -4,15 +4,17 @@ namespace ArrayAccess\TrayDigita\Database\Wrapper; use ArrayAccess\TrayDigita\Database\Connection; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerIndicateInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait; +use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; use Doctrine\DBAL\Driver\Connection as DoctrineConnection; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; use Throwable; -class ConnectionWrapper extends AbstractConnectionMiddleware +class ConnectionWrapper extends AbstractConnectionMiddleware implements ManagerIndicateInterface { use ManagerDispatcherTrait; @@ -28,19 +30,13 @@ protected function getPrefixNameEventIdentity(): ?string return 'connection'; } - protected function getManagerFromContainer(): ?ManagerInterface + + public function getManager(): ?ManagerInterface { - $container = $this->databaseConnection->getContainer(); - try { - $manager = $container->has(ManagerInterface::class) - ? $container->get(ManagerInterface::class) - : null; - } catch (Throwable) { - $manager = null; - } - return $manager instanceof ManagerInterface ? $manager : null; + return $this->databaseConnection->getManager(); } + public function prepare(string $sql): Statement { // @dispatch(connection.queryString) @@ -56,7 +52,10 @@ public function prepare(string $sql): Statement $this->databaseConnection ); - $prepared = parent::prepare($sql); + $prepared = new StatementWrapper( + $this, + parent::prepare($sql) + ); // @dispatch(connection.prepare) $this->dispatchCurrent( diff --git a/src/Database/Wrapper/DriverWrapper.php b/src/Database/Wrapper/DriverWrapper.php index c026460..81a4689 100644 --- a/src/Database/Wrapper/DriverWrapper.php +++ b/src/Database/Wrapper/DriverWrapper.php @@ -4,7 +4,9 @@ namespace ArrayAccess\TrayDigita\Database\Wrapper; use ArrayAccess\TrayDigita\Database\Connection; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerIndicateInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; +use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait; use ArrayAccess\TrayDigita\Traits\Service\CallStackTraceTrait; use ArrayAccess\TrayDigita\Util\Filter\Conversion; use DateTimeZone; @@ -21,9 +23,10 @@ use function preg_match; use function trim; -final class DriverWrapper extends AbstractDriverMiddleware +final class DriverWrapper extends AbstractDriverMiddleware implements ManagerIndicateInterface { - use CallStackTraceTrait; + use CallStackTraceTrait, + ManagerDispatcherTrait; public function __construct( private readonly Connection $databaseConnection, @@ -32,32 +35,27 @@ public function __construct( parent::__construct($wrappedDriver); } + protected function getPrefixNameEventIdentity(): ?string + { + return 'connection'; + } + public function getDatabaseConnection(): Connection { return $this->databaseConnection; } + public function getManager(): ?ManagerInterface + { + return $this->databaseConnection->getManager(); + } + public function connect(#[SensitiveParameter] array $params) : DoctrineConnection { $this->assertCallstack(); - $container = $this->databaseConnection->getContainer(); - try { - $manager = $container->has(ManagerInterface::class) - ? $container->get(ManagerInterface::class) - : null; - } catch (Throwable) { - $manager = null; - } - $manager = $manager instanceof ManagerInterface ? $manager : null; - // @dispatch(connection.beforeConnect) - $manager?->dispatch( - 'connection.beforeConnect', - $params, - $this->databaseConnection, - $this, - ); + $this->dispatchBefore($params, $this->databaseConnection); try { $connection = new ConnectionWrapper( @@ -67,8 +65,7 @@ public function connect(#[SensitiveParameter] array $params) : DoctrineConnectio $this->initConnection($connection); // @dispatch(connection.connect) - $manager?->dispatch( - 'connection.connect', + $this->dispatchCurrent( $params, $this->databaseConnection, $this, @@ -78,8 +75,7 @@ public function connect(#[SensitiveParameter] array $params) : DoctrineConnectio } finally { $this->resetCallstack(); // @dispatch(connection.afterConnect) - $manager?->dispatch( - 'connection.afterConnect', + $this->dispatchAfter( $params, $this->databaseConnection, $this, @@ -90,6 +86,8 @@ public function connect(#[SensitiveParameter] array $params) : DoctrineConnectio private function initConnection(Driver\Connection $connection): void { + // @dispatch(connection.beforeInitConnection) + $this->dispatchBefore($connection, $this->databaseConnection); $platform = $this->wrappedDriver->getDatabasePlatform(); $query = ''; $config = $this->databaseConnection->getDatabaseConfig(); @@ -148,8 +146,25 @@ private function initConnection(Driver\Connection $connection): void $query = "SET SESSION TIME_ZONE='$timezone';"; } try { - $query && $connection->exec($query); + if ($query) { + $result = $connection->exec($query); + } + // @dispatch(connection.initConnection) + $this->dispatchCurrent( + $connection, + $this->databaseConnection, + $query, + $result??null + ); } catch (Throwable) { + } finally { + // @dispatch(connection.afterInitConnection) + $this->dispatchAfter( + $connection, + $this->databaseConnection, + $query, + $result??null + ); } } } diff --git a/src/Database/Wrapper/EntityManagerWrapper.php b/src/Database/Wrapper/EntityManagerWrapper.php index 6480487..c5780ee 100644 --- a/src/Database/Wrapper/EntityManagerWrapper.php +++ b/src/Database/Wrapper/EntityManagerWrapper.php @@ -4,6 +4,7 @@ namespace ArrayAccess\TrayDigita\Database\Wrapper; use ArrayAccess\TrayDigita\Database\Connection; +use ArrayAccess\TrayDigita\Event\Interfaces\ManagerIndicateInterface; use ArrayAccess\TrayDigita\Event\Interfaces\ManagerInterface; use ArrayAccess\TrayDigita\Traits\Manager\ManagerDispatcherTrait; use Doctrine\Common\Collections\Selectable; @@ -17,7 +18,7 @@ use Throwable; use function get_object_vars; -class EntityManagerWrapper extends EntityManagerDecorator +class EntityManagerWrapper extends EntityManagerDecorator implements ManagerIndicateInterface { use ManagerDispatcherTrait; @@ -89,17 +90,9 @@ private function injectEntityManager( } } - protected function getManagerFromContainer() : ?ManagerInterface + public function getManager(): ?ManagerInterface { - $container = $this->databaseConnection->getContainer(); - try { - $manager = $container->has(ManagerInterface::class) - ? $container->get(ManagerInterface::class) - : null; - } catch (Throwable) { - $manager = null; - } - return $manager instanceof ManagerInterface ? $manager : null; + return $this->databaseConnection->getManager(); } /** diff --git a/src/Database/Wrapper/EntityRepositoryWrapper.php b/src/Database/Wrapper/EntityRepositoryWrapper.php index fcb0b82..0696e62 100644 --- a/src/Database/Wrapper/EntityRepositoryWrapper.php +++ b/src/Database/Wrapper/EntityRepositoryWrapper.php @@ -44,17 +44,9 @@ public function getRepository(): EntityRepository|ObjectRepository return $this->repository; } - protected function getManagerFromContainer(): ?ManagerInterface + public function getManager(): ?ManagerInterface { - $container = $this->databaseConnection->getContainer(); - try { - $manager = $container->has(ManagerInterface::class) - ? $container->get(ManagerInterface::class) - : null; - } catch (Throwable) { - $manager = null; - } - return $manager instanceof ManagerInterface ? $manager : null; + return $this->databaseConnection->getManager(); } public function find($id, $lockMode = null, $lockVersion = null) diff --git a/src/Database/Wrapper/ResultWrapper.php b/src/Database/Wrapper/ResultWrapper.php new file mode 100644 index 0000000..c339e17 --- /dev/null +++ b/src/Database/Wrapper/ResultWrapper.php @@ -0,0 +1,99 @@ +statementWrapper->getManager(); + } + + public function fetchNumeric() + { + return $this->dispatchWrap( + fn () => parent::fetchNumeric(), + $this->statementWrapper + ); + } + + public function fetchAssociative() + { + return $this->dispatchWrap( + fn () => parent::fetchAssociative(), + $this->statementWrapper + ); + } + + public function fetchOne() + { + return $this->dispatchWrap( + fn () => parent::fetchOne(), + $this->statementWrapper + ); + } + + public function fetchAllNumeric(): array + { + return $this->dispatchWrap( + fn () => parent::fetchAllNumeric(), + $this->statementWrapper + ); + } + + public function fetchAllAssociative(): array + { + return $this->dispatchWrap( + fn () => parent::fetchAllAssociative(), + $this->statementWrapper + ); + } + + public function fetchFirstColumn(): array + { + return $this->dispatchWrap( + fn () => parent::fetchFirstColumn(), + $this->statementWrapper + ); + } + + public function rowCount(): int + { + return $this->dispatchWrap( + fn () => parent::rowCount(), + $this->statementWrapper + ); + } + + public function columnCount(): int + { + return $this->dispatchWrap( + fn () => parent::columnCount(), + $this->statementWrapper + ); + } + + public function free(): void + { + $this->dispatchWrap( + fn () => parent::free(), + $this->statementWrapper + ); + } +} diff --git a/src/Database/Wrapper/StatementWrapper.php b/src/Database/Wrapper/StatementWrapper.php new file mode 100644 index 0000000..93b4148 --- /dev/null +++ b/src/Database/Wrapper/StatementWrapper.php @@ -0,0 +1,42 @@ +connectionWrapper->getManager(); + } + + public function execute($params = null): Result + { + return $this->dispatchWrap( + fn () => parent::execute($params), + $params, + $this->connectionWrapper + ); + } +} diff --git a/src/Lang/default.pot b/src/Lang/default.pot index a514121..142c753 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-19 03:54+0700\n" +"POT-Creation-Date: 2023-10-19 21:20+0700\n" "PO-Revision-Date: 2023-09-24 19:00+0700\n" "Last-Translator: ArrayAccess\n" "Language-Team: ArrayAccess\n" @@ -48,21 +48,21 @@ msgctxt "benchmark" msgid "Service" msgstr "" -#: Benchmark/Middlewares/DebuggingMiddleware.php:201 -#: Benchmark/Middlewares/DebuggingMiddleware.php:205 -#: Benchmark/Middlewares/DebuggingMiddleware.php:255 +#: Benchmark/Middlewares/DebuggingMiddleware.php:212 +#: Benchmark/Middlewares/DebuggingMiddleware.php:216 +#: Benchmark/Middlewares/DebuggingMiddleware.php:267 msgctxt "benchmark" msgid "rendered time" msgstr "" -#: Benchmark/Middlewares/DebuggingMiddleware.php:203 -#: Benchmark/Middlewares/DebuggingMiddleware.php:207 -#: Benchmark/Middlewares/DebuggingMiddleware.php:262 +#: Benchmark/Middlewares/DebuggingMiddleware.php:214 +#: Benchmark/Middlewares/DebuggingMiddleware.php:218 +#: Benchmark/Middlewares/DebuggingMiddleware.php:274 msgctxt "benchmark" msgid "memory usage" msgstr "" -#: Benchmark/Middlewares/DebuggingMiddleware.php:260 +#: Benchmark/Middlewares/DebuggingMiddleware.php:272 msgctxt "benchmark" msgid "peak memory usage" msgstr "" @@ -611,29 +611,29 @@ msgctxt "console" msgid "Press %s to exit." msgstr "" -#: Console/Command/ChecksumGenerator.php:60 +#: Console/Command/ChecksumGenerator.php:61 msgctxt "console" msgid "Create list of core file checksums." msgstr "" -#: Console/Command/ChecksumGenerator.php:69 +#: Console/Command/ChecksumGenerator.php:70 msgctxt "console" msgid "Display checksums on terminal without writing to disk" msgstr "" -#: Console/Command/ChecksumGenerator.php:79 +#: Console/Command/ChecksumGenerator.php:80 #, php-format msgctxt "console" msgid "The %s creating checksum files on %s directory." msgstr "" -#: Console/Command/ChecksumGenerator.php:121 +#: Console/Command/ChecksumGenerator.php:122 #, php-format msgctxt "console" msgid "Files will be put in directory: %s" msgstr "" -#: Console/Command/ChecksumGenerator.php:133 +#: Console/Command/ChecksumGenerator.php:134 #: Console/Command/CommandGenerator.php:333 #: Console/Command/ControllerGenerator.php:296 #: Console/Command/DatabaseChecker.php:1255 @@ -647,7 +647,7 @@ msgctxt "console" msgid "Are you sure to continue (Yes/No)?" msgstr "" -#: Console/Command/ChecksumGenerator.php:148 +#: Console/Command/ChecksumGenerator.php:149 #: Console/Command/CommandGenerator.php:346 #: Console/Command/ControllerGenerator.php:309 #: Console/Command/DatabaseChecker.php:1270 @@ -661,7 +661,7 @@ msgctxt "console" msgid "Please enter valid answer! (Yes / No)" msgstr "" -#: Console/Command/ChecksumGenerator.php:160 +#: Console/Command/ChecksumGenerator.php:161 #: Console/Command/CommandGenerator.php:390 #: Console/Command/ControllerGenerator.php:351 #: Console/Command/DatabaseChecker.php:1282 @@ -675,12 +675,12 @@ msgctxt "console" msgid "Operation cancelled!" msgstr "" -#: Console/Command/ChecksumGenerator.php:271 +#: Console/Command/ChecksumGenerator.php:273 msgctxt "console" msgid "Done!" msgstr "" -#: Console/Command/ChecksumGenerator.php:283 +#: Console/Command/ChecksumGenerator.php:285 msgctxt "console" msgid "Source" msgstr "" @@ -1887,3 +1887,128 @@ msgstr "" msgctxt "template" msgid "unknown" msgstr "" + +#: Uploader/Chunk.php:132 +msgctxt "chunk-uploader" +msgid "Storage directory could not be empty or whitespace only." +msgstr "" + +#: Uploader/Chunk.php:142 +#, php-format +msgctxt "chunk-uploader" +msgid "Directory %s is not exist" +msgstr "" + +#: Uploader/Chunk.php:154 +#, php-format +msgctxt "chunk-uploader" +msgid "Directory %s is not writable" +msgstr "" + +#: Uploader/ChunkHandler.php:116 Uploader/ChunkProcessor.php:223 +#, php-format +msgctxt "chunk-uploader" +msgid "Request id \"%s\" is not valid" +msgstr "" + +#: Uploader/ChunkHandler.php:224 +msgctxt "chunk-uploader" +msgid "Cache upload storage is not writable." +msgstr "" + +#: Uploader/ChunkHandler.php:276 +msgctxt "chunk-uploader" +msgid "Upload cache file is not writable." +msgstr "" + +#: Uploader/ChunkHandler.php:290 +msgctxt "chunk-uploader" +msgid "Can not create cached stream." +msgstr "" + +#: Uploader/ChunkHandler.php:301 +msgctxt "chunk-uploader" +msgid "Cache file has been locked." +msgstr "" + +#: Uploader/ChunkHandler.php:395 +msgctxt "chunk-uploader" +msgid "Offset upload position is invalid." +msgstr "" + +#: Uploader/ChunkHandler.php:427 +msgctxt "chunk-uploader" +msgid "Source uploaded file does not exist." +msgstr "" + +#: Uploader/ChunkHandler.php:447 +#, php-format +msgctxt "chunk-uploader" +msgid "Upload cache file is not ready to move : (%d)." +msgstr "" + +#: Uploader/ChunkHandler.php:496 +#, php-format +msgctxt "chunk-uploader" +msgid "Target file \"%s\" is not writable." +msgstr "" + +#: Uploader/ChunkHandler.php:516 +#, php-format +msgctxt "chunk-uploader" +msgid "Target directory \"%s\" is not writable." +msgstr "" + +#: Uploader/ChunkProcessor.php:46 +msgctxt "chunk-uploader" +msgid "Uploaded files does not contain file name." +msgstr "" + +#: Uploader/ChunkProcessor.php:137 +#, php-format +msgctxt "chunk-uploader" +msgid "Content-Range \"%s\" is invalid" +msgstr "" + +#: Uploader/ChunkProcessor.php:152 +#, php-format +msgctxt "chunk-uploader" +msgid "Content-Range unit \"%s\" is invalid" +msgstr "" + +#: Uploader/ChunkProcessor.php:163 +msgctxt "chunk-uploader" +msgid "System does not support unknown size" +msgstr "" + +#: Uploader/ChunkProcessor.php:184 +#, php-format +msgctxt "chunk-uploader" +msgid "Uploaded file size is bigger than allowed size: %s." +msgstr "" + +#: Uploader/ChunkProcessor.php:200 +#, php-format +msgctxt "chunk-uploader" +msgid "Uploaded file size range is less than allowed minimum size: %s." +msgstr "" + +#: Uploader/ChunkProcessor.php:214 +msgctxt "chunk-uploader" +msgid "Content-Range start bytes must be zero without Request-Id." +msgstr "" + +#: Uploader/ChunkProcessor.php:234 +msgctxt "chunk-uploader" +msgid "Content-Range start bytes must be greater zero with Request-Id." +msgstr "" + +#: Uploader/ChunkProcessor.php:246 +msgctxt "chunk-uploader" +msgid "Uploaded file size is bigger than ending size." +msgstr "" + +#: Uploader/ChunkProcessor.php:255 +msgctxt "chunk-uploader" +msgid "Range size is bigger than file size." +msgstr "" diff --git a/src/Lang/id.po b/src/Lang/id.po index 89067b9..08dbda1 100644 --- a/src/Lang/id.po +++ b/src/Lang/id.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: TrayDigita 1.0.0\n" -"POT-Creation-Date: 2023-10-19 03:54+0700\n" -"PO-Revision-Date: 2023-10-19 03:55+0700\n" +"POT-Creation-Date: 2023-10-19 21:21+0700\n" +"PO-Revision-Date: 2023-10-19 21:21+0700\n" "Last-Translator: \n" "Language-Team: ArrayAccess\n" "Language: id\n" @@ -47,21 +47,21 @@ msgctxt "benchmark" msgid "Service" msgstr "Layanan" -#: Benchmark/Middlewares/DebuggingMiddleware.php:201 -#: Benchmark/Middlewares/DebuggingMiddleware.php:205 -#: Benchmark/Middlewares/DebuggingMiddleware.php:255 +#: Benchmark/Middlewares/DebuggingMiddleware.php:212 +#: Benchmark/Middlewares/DebuggingMiddleware.php:216 +#: Benchmark/Middlewares/DebuggingMiddleware.php:267 msgctxt "benchmark" msgid "rendered time" msgstr "waktu pemuatan" -#: Benchmark/Middlewares/DebuggingMiddleware.php:203 -#: Benchmark/Middlewares/DebuggingMiddleware.php:207 -#: Benchmark/Middlewares/DebuggingMiddleware.php:262 +#: Benchmark/Middlewares/DebuggingMiddleware.php:214 +#: Benchmark/Middlewares/DebuggingMiddleware.php:218 +#: Benchmark/Middlewares/DebuggingMiddleware.php:274 msgctxt "benchmark" msgid "memory usage" msgstr "penggunaan memori" -#: Benchmark/Middlewares/DebuggingMiddleware.php:260 +#: Benchmark/Middlewares/DebuggingMiddleware.php:272 msgctxt "benchmark" msgid "peak memory usage" msgstr "penggunaan puncak memori" @@ -621,29 +621,29 @@ msgctxt "console" msgid "Press %s to exit." msgstr "Tekan %s untuk keluar." -#: Console/Command/ChecksumGenerator.php:60 +#: Console/Command/ChecksumGenerator.php:61 msgctxt "console" msgid "Create list of core file checksums." msgstr "Membuat berkas daftar inti ceksum." -#: Console/Command/ChecksumGenerator.php:69 +#: Console/Command/ChecksumGenerator.php:70 msgctxt "console" msgid "Display checksums on terminal without writing to disk" msgstr "Tampilkan checksum di terminal tanpa menulis ke disk" -#: Console/Command/ChecksumGenerator.php:79 +#: Console/Command/ChecksumGenerator.php:80 #, php-format msgctxt "console" msgid "The %s creating checksum files on %s directory." msgstr "%s membuat berkas ceksum pada direktori %s." -#: Console/Command/ChecksumGenerator.php:121 +#: Console/Command/ChecksumGenerator.php:122 #, php-format msgctxt "console" msgid "Files will be put in directory: %s" msgstr "Berkas akan diletakkan pada direktori: %s" -#: Console/Command/ChecksumGenerator.php:133 +#: Console/Command/ChecksumGenerator.php:134 #: Console/Command/CommandGenerator.php:333 #: Console/Command/ControllerGenerator.php:296 #: Console/Command/DatabaseChecker.php:1255 @@ -657,7 +657,7 @@ msgctxt "console" msgid "Are you sure to continue (Yes/No)?" msgstr "Apakah anda yakin ingin melanjutkan (Yes/No)?" -#: Console/Command/ChecksumGenerator.php:148 +#: Console/Command/ChecksumGenerator.php:149 #: Console/Command/CommandGenerator.php:346 #: Console/Command/ControllerGenerator.php:309 #: Console/Command/DatabaseChecker.php:1270 @@ -671,7 +671,7 @@ msgctxt "console" msgid "Please enter valid answer! (Yes / No)" msgstr "Mohon masukkan jawaban yang benar! (Yes/No)" -#: Console/Command/ChecksumGenerator.php:160 +#: Console/Command/ChecksumGenerator.php:161 #: Console/Command/CommandGenerator.php:390 #: Console/Command/ControllerGenerator.php:351 #: Console/Command/DatabaseChecker.php:1282 @@ -685,12 +685,12 @@ msgctxt "console" msgid "Operation cancelled!" msgstr "Operasi dibatalkan!" -#: Console/Command/ChecksumGenerator.php:271 +#: Console/Command/ChecksumGenerator.php:273 msgctxt "console" msgid "Done!" msgstr "Selesai!" -#: Console/Command/ChecksumGenerator.php:283 +#: Console/Command/ChecksumGenerator.php:285 msgctxt "console" msgid "Source" msgstr "Sumber" @@ -1956,6 +1956,134 @@ msgctxt "template" msgid "unknown" msgstr "tak diketahui" +#: Uploader/Chunk.php:132 +msgctxt "chunk-uploader" +msgid "Storage directory could not be empty or whitespace only." +msgstr "Direktori penyimpanan tidak dapat kosong atau berupa whitespace saja." + +#: Uploader/Chunk.php:142 +#, php-format +msgctxt "chunk-uploader" +msgid "Directory %s is not exist" +msgstr "Direktori %s tidak ada" + +#: Uploader/Chunk.php:154 +#, php-format +msgctxt "chunk-uploader" +msgid "Directory %s is not writable" +msgstr "Direktori %s tidak dapat ditulisi" + +#: Uploader/ChunkHandler.php:116 Uploader/ChunkProcessor.php:223 +#, php-format +msgctxt "chunk-uploader" +msgid "Request id \"%s\" is not valid" +msgstr "Request id \"%s\" tidak sah" + +#: Uploader/ChunkHandler.php:224 +msgctxt "chunk-uploader" +msgid "Cache upload storage is not writable." +msgstr "Direktori penyimpanan cache tidak dapat ditulisi." + +#: Uploader/ChunkHandler.php:276 +msgctxt "chunk-uploader" +msgid "Upload cache file is not writable." +msgstr "Berkas cache unggahan tidak dapat ditulisi." + +#: Uploader/ChunkHandler.php:290 +msgctxt "chunk-uploader" +msgid "Can not create cached stream." +msgstr "Tidak dapat membuat cached stream." + +#: Uploader/ChunkHandler.php:301 +msgctxt "chunk-uploader" +msgid "Cache file has been locked." +msgstr "Berkas cache terkunci." + +#: Uploader/ChunkHandler.php:395 +msgctxt "chunk-uploader" +msgid "Offset upload position is invalid." +msgstr "Posisi offset unggahan tidak sah." + +#: Uploader/ChunkHandler.php:427 +msgctxt "chunk-uploader" +msgid "Source uploaded file does not exist." +msgstr "Berkas sumber yang diunggah tidak ada." + +#: Uploader/ChunkHandler.php:447 +#, php-format +msgctxt "chunk-uploader" +msgid "Upload cache file is not ready to move : (%d)." +msgstr "Berkas cache yang diunggah belum siap dipindahkan : (%d)." + +#: Uploader/ChunkHandler.php:496 +#, php-format +msgctxt "chunk-uploader" +msgid "Target file \"%s\" is not writable." +msgstr "Berkas tujuan \"%s\" tidak dapat ditulisi." + +#: Uploader/ChunkHandler.php:516 +#, php-format +msgctxt "chunk-uploader" +msgid "Target directory \"%s\" is not writable." +msgstr "Direktori tujuan \"%s\" tidak dapat ditulisi." + +#: Uploader/ChunkProcessor.php:46 +msgctxt "chunk-uploader" +msgid "Uploaded files does not contain file name." +msgstr "Berkas terunggah tidak mempunyai nama berkas." + +#: Uploader/ChunkProcessor.php:137 +#, php-format +msgctxt "chunk-uploader" +msgid "Content-Range \"%s\" is invalid" +msgstr "Content-Range \"%s\" tidak sah" + +#: Uploader/ChunkProcessor.php:152 +#, php-format +msgctxt "chunk-uploader" +msgid "Content-Range unit \"%s\" is invalid" +msgstr "Unit Content-Range \"%s\" tidak sah" + +#: Uploader/ChunkProcessor.php:163 +msgctxt "chunk-uploader" +msgid "System does not support unknown size" +msgstr "Sistem tidak mendukung ukuran yang tidak diketahui" + +#: Uploader/ChunkProcessor.php:184 +#, php-format +msgctxt "chunk-uploader" +msgid "Uploaded file size is bigger than allowed size: %s." +msgstr "" +"Ukuran berkas yang diunggah lebih besar dari ukuran yang diperbolehkan: %s." + +#: Uploader/ChunkProcessor.php:200 +#, php-format +msgctxt "chunk-uploader" +msgid "Uploaded file size range is less than allowed minimum size: %s." +msgstr "" +"Ukuran rentang berkas yang diunggah kurang dari ukuran minimum yang " +"diperbolehkan: %s." + +#: Uploader/ChunkProcessor.php:214 +msgctxt "chunk-uploader" +msgid "Content-Range start bytes must be zero without Request-Id." +msgstr "Content-Range byte awal harus nol atau tanpa Request-Id." + +#: Uploader/ChunkProcessor.php:234 +msgctxt "chunk-uploader" +msgid "Content-Range start bytes must be greater zero with Request-Id." +msgstr "Content-Range awal harus lebih besar dari nol dengan Id Permintaan." + +#: Uploader/ChunkProcessor.php:246 +msgctxt "chunk-uploader" +msgid "Uploaded file size is bigger than ending size." +msgstr "Ukuran berkas yang diunggah lebih besar dari ukuran akhir." + +#: Uploader/ChunkProcessor.php:255 +msgctxt "chunk-uploader" +msgid "Range size is bigger than file size." +msgstr "Ukuran rentang lebih besar dari ukuran berkas." + #~ msgctxt "console" #~ msgid "No task in queue" #~ msgstr "Tidak ada tugas dalam antrian" diff --git a/src/Traits/Manager/ManagerDispatcherTrait.php b/src/Traits/Manager/ManagerDispatcherTrait.php index 26bc7f1..244e151 100644 --- a/src/Traits/Manager/ManagerDispatcherTrait.php +++ b/src/Traits/Manager/ManagerDispatcherTrait.php @@ -24,65 +24,13 @@ protected function getPrefixNameEventIdentity(): ?string return null; } - protected function getManagerFromContainer(): ?ManagerInterface - { - return ContainerHelper::getNull(ManagerInterface::class, $this->getContainer()); - } - - protected function getInternalMethodManager() : ?ManagerInterface - { - if ($this->internalMethodManager !== null) { - $method = $this->internalMethodManager?:null; - return $method ? $this->$method() : null; - } - - if ($this instanceof ManagerIndicateInterface) { - $this->internalMethodManager = 'getManager'; - return $this->getManager(); - } - - $manager = $this->getManagerFromContainer(); - if ($manager) { - $this->internalMethodManager = 'getManagerFromContainer'; - return $manager; - } - - $ref = new ReflectionObject($this); - if (!$ref->hasMethod('getManager')) { - return null; - } - $method = $ref->getMethod('getManager'); - $returnType = $method->getNumberOfRequiredParameters() === 0 - ? $method->getReturnType() - : null; - if (!$returnType) { - return null; - } - $types = []; - if ($returnType instanceof ReflectionNamedType) { - $types = [$returnType]; - } elseif ($returnType instanceof ReflectionUnionType) { - $types = $returnType->getTypes(); - } - foreach ($types as $type) { - if ($type->isBuiltin()) { - continue; - } - if ($type->getName() === ManagerInterface::class - || is_subclass_of($type->getName(), ManagerInterface::class) - ) { - $this->internalMethodManager = $type->getName(); - return $this->{$this->internalMethodManager}(); - } - } - return null; - } + abstract public function getManager() : ?ManagerInterface; protected function dispatchEvent( string $eventName, ...$arguments ) { - $manager = $this->getInternalMethodManager(); + $manager = $this->getManager(); if (!$manager) { return count($arguments) > 0 ? reset($arguments) @@ -133,4 +81,16 @@ protected function dispatchAfter(...$arguments) { return $this->doDispatchMethod('after', ...$arguments); } + + protected function dispatchWrap(callable $callback, ...$arguments) + { + try { + $this->doDispatchMethod('before', ...$arguments); + $arguments[] = $callback(); + $this->doDispatchMethod(null, ...$arguments); + return end($arguments); + } finally { + $this->doDispatchMethod('after', ...$arguments); + } + } } diff --git a/src/Uploader/Chunk.php b/src/Uploader/Chunk.php index 2e58603..9a649ec 100644 --- a/src/Uploader/Chunk.php +++ b/src/Uploader/Chunk.php @@ -12,6 +12,7 @@ use ArrayAccess\TrayDigita\Traits\Service\TranslatorTrait; use ArrayAccess\TrayDigita\Uploader\Exceptions\DirectoryUnWritAbleException; use ArrayAccess\TrayDigita\Uploader\Exceptions\NotExistsDirectoryException; +use ArrayAccess\TrayDigita\Util\Filter\Consolidation; use ArrayAccess\TrayDigita\Util\Filter\ContainerHelper; use DirectoryIterator; use Psr\Container\ContainerInterface; @@ -19,8 +20,11 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use function is_dir; +use function is_int; use function is_string; use function is_writable; +use function max; +use function min; use function realpath; use function sprintf; use function sys_get_temp_dir; @@ -38,6 +42,10 @@ class Chunk implements ManagerAllocatorInterface, ContainerIndicateInterface */ const MAX_AGE_FILE = 18000; + const MIN_FILE_SIZE = 1024; + + const DEFAULT_MIN_FILE_SIZE = 512000; + /** * Maximum size for unlink */ @@ -53,6 +61,11 @@ class Chunk implements ManagerAllocatorInterface, ContainerIndicateInterface */ public readonly string $partialExtension; + /** + * @var string + */ + public readonly string $partialMetaExtension; + /** * @var int */ @@ -60,10 +73,18 @@ class Chunk implements ManagerAllocatorInterface, ContainerIndicateInterface /** * @var ?int $limitMaxFileSize total max file size null as unlimited - * default : 134217728 bytes or 128MB + * default : 134217728 bytes or 128MiB */ private ?int $limitMaxFileSize = 134217728; + /** + * @var ?int minimum size limit null as unlimited + * default: 512000 as 500Kib + */ + private ?int $limitMinimumFileSize = self::DEFAULT_MIN_FILE_SIZE; + + private int $maxUploadFileSize; + public function __construct( protected ContainerInterface $container, ?string $storageDir = null @@ -88,12 +109,14 @@ public function __construct( $this->setManager($manager); } $this->partialExtension = 'partial'; + $this->partialMetaExtension = $this->partialExtension . '.meta'; $storageDir = $storageDir??sys_get_temp_dir(); $this->assertDirectory($storageDir); $storageDir = (realpath($storageDir)??$storageDir); $this->uploadCacheStorageDirectory = $storageDir . DIRECTORY_SEPARATOR . self::SUFFIX_STORAGE_DIRECTORY; + $this->maxUploadFileSize = Consolidation::getMaxUploadSize(); } public function getContainer(): ?ContainerInterface @@ -105,14 +128,20 @@ private function assertDirectory(string $directory): void { if (trim($directory) === '') { throw new EmptyArgumentException( - 'Storage directory could not be empty or whitespace only.' + $this->translateContext( + 'Storage directory could not be empty or whitespace only.', + 'chunk-uploader' + ) ); } if (!is_dir($directory)) { throw new NotExistsDirectoryException( $directory, sprintf( - 'Directory %s is not exist', + $this->translateContext( + 'Directory %s is not exist', + 'chunk-uploader' + ), $directory ) ); @@ -121,7 +150,10 @@ private function assertDirectory(string $directory): void throw new DirectoryUnWritAbleException( $directory, sprintf( - 'Directory %s is not writable', + $this->translateContext( + 'Directory %s is not writable', + 'chunk-uploader' + ), $directory ) ); @@ -153,9 +185,42 @@ public function getLimitMaxFileSize(): ?int return $this->limitMaxFileSize; } - public function setLimitMaxFileSize(?int $limitMaxFileSize): void + public function setLimitMaxFileSize(?int $limitMaxFileSize): ?int { + if (is_int($limitMaxFileSize)) { + // minimum 500KiB + $limitMaxFileSize = max($limitMaxFileSize, self::DEFAULT_MIN_FILE_SIZE); + if (is_int($this->limitMinimumFileSize) + && $limitMaxFileSize < $this->limitMinimumFileSize + ) { + // assert min + $this->limitMinimumFileSize = min($limitMaxFileSize, $this->limitMinimumFileSize); + } + } $this->limitMaxFileSize = $limitMaxFileSize; + return $this->limitMaxFileSize; + } + + public function getMaxUploadFileSize(): int + { + return $this->maxUploadFileSize; + } + + public function setLimitMinimumFileSize(?int $limitMinFileSize): ?int + { + if (is_int($limitMinFileSize)) { + $limitMinFileSize = min($limitMinFileSize, $this->getMaxUploadFileSize()); + $limitMinFileSize = min($limitMinFileSize, $this->getLimitMaxFileSize()); + // minimum is 1024 + $limitMinFileSize = max($limitMinFileSize, self::MIN_FILE_SIZE); + } + $this->limitMinimumFileSize = $limitMinFileSize; + return $this->limitMinimumFileSize; + } + + public function getLimitMinimumFileSize(): ?int + { + return $this->limitMinimumFileSize; } public function appendResponseBytes(ResponseInterface $response): ResponseInterface @@ -176,8 +241,8 @@ public function createProcessor( public function clean(?int $max = null) : int { - $max ??= $this->maxDeletionCount; - if (!is_dir($this->uploadCacheStorageDirectory) || $max <= 0) { + $max ??= $this->getMaxDeletionCount(); + if ($max <= 0 || !is_dir($this->uploadCacheStorageDirectory)) { return 0; } @@ -185,9 +250,14 @@ public function clean(?int $max = null) : int foreach (new DirectoryIterator( $this->uploadCacheStorageDirectory ) as $item) { + if ($max <= 0) { + break; + } + $isPartial = $item->getExtension() !== $this->partialExtension; + $isPartialMeta = $item->getExtension() !== $this->partialMetaExtension; if ($item->isDot() - || $item->getExtension() !== $this->partialExtension || $item->getBasename()[0] === '.' + || (!$isPartial && !$isPartialMeta) || !$item->isFile() || $item->isLink() ) { @@ -199,8 +269,8 @@ public function clean(?int $max = null) : int if (!$item->isWritable()) { continue; } - if ($max-- < 0) { - break; + if ($isPartial) { + $max--; } $deleted++; unlink($item->getRealPath()); diff --git a/src/Uploader/ChunkHandler.php b/src/Uploader/ChunkHandler.php index 5c22a68..b97dbef 100644 --- a/src/Uploader/ChunkHandler.php +++ b/src/Uploader/ChunkHandler.php @@ -16,11 +16,23 @@ use ArrayAccess\TrayDigita\Util\Filter\Consolidation; use Psr\Http\Message\ResponseInterface; use SplFileInfo; +use function file_get_contents; +use function file_put_contents; +use function filesize; +use function is_array; use function is_file; +use function is_float; +use function is_int; +use function is_string; +use function json_decode; +use function json_encode; +use function microtime; use function preg_match; use function preg_quote; use function sprintf; use function substr; +use function unlink; +use const JSON_UNESCAPED_SLASHES; class ChunkHandler { @@ -41,6 +53,25 @@ class ChunkHandler */ public readonly string $targetCacheFile; + public readonly string $targetCacheMetaFile; + + /** + * @var array{ + * first_time: ?float, + * size: ?int, + * count: ?int, + * mimetype: ?string, + * timing: array + * } + */ + private array $metadata = [ + 'first_time' => null, + 'size' => null, + 'count' => null, + 'mimetype' => null, + 'timing' => [], + ]; + /** * @var int */ @@ -81,7 +112,10 @@ public function __construct( if (!$this->processor->requestIdHeader->valid) { throw new InvalidRequestId( sprintf( - 'Request id "%s" is not valid', + $this->processor->chunk->translateContext( + 'Request id "%s" is not valid', + 'chunk-uploader' + ), $this->processor->requestIdHeader->header ) ); @@ -93,6 +127,11 @@ public function __construct( $this->processor->requestIdHeader->header, $this->processor->chunk->partialExtension ); + $this->targetCacheMetaFile = sprintf( + '%1$s.%2$s', + $this->targetCacheFile, + $this->processor->chunk->partialMetaExtension + ); } public function appendResponseHeader(ResponseInterface $response) : ResponseInterface @@ -122,12 +161,35 @@ public function getLastTarget(): ?string return $this->lastTarget; } + /** + * @return array{ + * first_time: ?float, + * size: ?int, + * count: ?int, + * mimetype: ?string, + * timing: array + * } + */ + public function getMetadata(): array + { + return $this->metadata; + } + public function deletePartial(): bool { + $status = false; if (is_file($this->targetCacheFile)) { - return unlink($this->targetCacheFile); + $status = Consolidation::callbackReduceError( + fn () => unlink($this->targetCacheFile) + ); + } + if (is_file($this->targetCacheMetaFile)) { + $new_status = Consolidation::callbackReduceError( + fn () => unlink($this->targetCacheMetaFile) + ); + $status = $status || $new_status; } - return false; + return $status; } /** @@ -158,7 +220,10 @@ protected function check() : int if (!is_writable($uploadDirectory)) { $this->status = self::STATUS_NOT_READY; throw new DirectoryUnWritAbleException( - 'Cache upload storage is not writable.' + $this->processor->chunk->translateContext( + 'Cache upload storage is not writable.', + 'chunk-uploader' + ) ); } @@ -168,9 +233,11 @@ protected function check() : int ); $this->status = self::STATUS_READY; - $this->size = is_file($this->targetCacheFile) - ? filesize($this->targetCacheFile) - : 0; + if (is_file($this->targetCacheFile)) { + $this->size = filesize($this->targetCacheFile); + } else { + $this->size = 0; + } return $this->status; } @@ -205,7 +272,10 @@ private function writeResource(string $mode) : int $this->status = self::STATUS_FAIL; throw new FileUnWritAbleException( $this->targetCacheFile, - 'Upload cache file is not writable.' + $this->processor->chunk->translateContext( + 'Upload cache file is not writable.', + 'chunk-uploader' + ) ); } @@ -216,7 +286,10 @@ private function writeResource(string $mode) : int if (!is_resource($this->cacheResource)) { $this->status = self::STATUS_FAIL; throw new SourceFileFailException( - 'Can not create cached stream.' + $this->processor->chunk->translateContext( + 'Can not create cached stream.', + 'chunk-uploader' + ) ); } @@ -224,7 +297,10 @@ private function writeResource(string $mode) : int if ($wouldBlock) { throw new FileLockedException( $this->targetCacheFile, - 'Cache file has been locked.' + $this->processor->chunk->translateContext( + 'Cache file has been locked.', + 'chunk-uploader' + ) ); } @@ -237,10 +313,58 @@ private function writeResource(string $mode) : int while (!$uploadedStream->eof()) { $this->written += (int) fwrite($this->cacheResource, $uploadedStream->read(2048)); } - + $isFirst = $this->size === 0; $stat = Consolidation::callbackReduceError(fn () => fstat($this->cacheResource)); $this->size = $stat ? (int) ($stat['size']??$this->size+$this->written) : ($this->size+$this->written); flock($this->cacheResource, LOCK_EX); + $written = null; + $time = $_SERVER['REQUEST_FLOAT_TIME']??null; + $time = is_float($time) ? $time : microtime(true); + if (is_file($this->targetCacheMetaFile)) { + $meta = Consolidation::callbackReduceError(fn () => json_decode( + file_get_contents($this->targetCacheMetaFile), + true + )); + $valid = is_array($meta) + && isset($meta['first_time'], $meta['mimetype'], $meta['count'], $meta['timing']) + && is_string($meta['mimetype']) + && is_float($meta['first_time']) + && is_int($meta['count']) + && is_array($meta['timing']) + && count($meta['timing']) === $meta['count'] + && preg_match('~^[^/]+/~', $meta['mimetype']); + if (!$valid) { + Consolidation::callbackReduceError(fn () => unlink($this->targetCacheMetaFile)); + } else { + $written = $meta; + $written['count'] += 1; + $written['timing'][] = [ + 'time' => $time, + 'written' => $this->written, + 'size' => $this->size + ]; + } + } elseif ($isFirst) { + $written = [ + 'first_time' => $time, + 'mimetype' => $this->processor->uploadedFile->getClientMediaType(), + 'count' => 1, + 'timing' => [ + [ + 'time' => $time, + 'written' => $this->written, + 'size' => $this->size + ] + ] + ]; + } + if (is_array($written)) { + $this->metadata = $written; + Consolidation::callbackReduceError(fn () => file_put_contents( + $this->targetCacheMetaFile, + json_encode($written, JSON_UNESCAPED_SLASHES) + )); + } return $this->written; } @@ -267,7 +391,10 @@ public function start(?int $position = null): int throw new InvalidOffsetPositionException( $position, $this->size, - 'Offset upload position is invalid.' + $this->processor->chunk->translateContext( + 'Offset upload position is invalid.', + 'chunk-uploader' + ) ); } @@ -296,7 +423,10 @@ public function put( if (!is_file($movedFile?:$this->targetCacheFile)) { throw new SourceFileNotFoundException( $movedFile?:$this->targetCacheFile, - 'Source uploaded file does not exist.' + $this->processor->chunk->translateContext( + 'Source uploaded file does not exist.', + 'chunk-uploader' + ) ); } @@ -313,7 +443,10 @@ public function put( if (!$ready) { throw new SourceFileMovedException( sprintf( - 'Upload cache file is not ready to move : (%d).', + $this->processor->chunk->translateContext( + 'Upload cache file is not ready to move : (%d).', + 'chunk-uploader' + ), $this->status ) ); @@ -359,7 +492,10 @@ public function put( throw new FileUnWritAbleException( $target, sprintf( - '%s is not writable.', + $this->processor->chunk->translateContext( + 'Target file "%s" is not writable.', + 'chunk-uploader' + ), $target ) ); @@ -376,7 +512,10 @@ public function put( throw new DirectoryUnWritAbleException( $targetDirectory, sprintf( - '%s is not writable.', + $this->processor->chunk->translateContext( + 'Target directory "%s" is not writable.', + 'chunk-uploader' + ), $target ) ); @@ -392,6 +531,12 @@ public function put( ); } + if (is_file($this->targetCacheMetaFile)) { + Consolidation::callbackReduceError( + fn() => unlink($this->targetCacheMetaFile) + ); + } + $this->lastTarget = null; if ($result) { $this->lastTarget = realpath($target) ?: $target; diff --git a/src/Uploader/ChunkProcessor.php b/src/Uploader/ChunkProcessor.php index a37a1e8..72ab374 100644 --- a/src/Uploader/ChunkProcessor.php +++ b/src/Uploader/ChunkProcessor.php @@ -19,6 +19,7 @@ use function is_int; use function is_string; use function sprintf; +use function var_dump; final class ChunkProcessor { @@ -41,7 +42,10 @@ public function __construct( $clientFileName = $this->uploadedFile->getClientFilename(); if ($clientFileName === null) { throw new UnsupportedArgumentException( - 'Uploaded files does not contain file name.' + $this->chunk->translateContext( + 'Uploaded files does not contain file name.', + 'chunk-uploader' + ) ); } if ($requestIdHeader !== null) { @@ -129,7 +133,10 @@ public function getHandler() : ChunkHandler if (!$this->contentRangeHeader->valid) { throw new InvalidContentRange( sprintf( - 'Content range "%s" is invalid', + $this->chunk->translateContext( + 'Content-Range "%s" is invalid', + 'chunk-uploader' + ), $this->contentRangeHeader->header ) ); @@ -141,7 +148,10 @@ public function getHandler() : ChunkHandler )) { throw new InvalidContentRange( sprintf( - 'Content range unit "%s" is invalid', + $this->chunk->translateContext( + 'Content-Range unit "%s" is invalid', + 'chunk-uploader' + ), $this->contentRangeHeader->unit ) ); @@ -149,40 +159,70 @@ public function getHandler() : ChunkHandler if (!is_int($this->contentRangeHeader->size)) { throw new ContentRangeIsNotFulFilledException( - 'System does not support unknown size' + $this->chunk->translateContext( + 'System does not support unknown size', + 'chunk-uploader' + ) ); } } $size = $this->contentRangeHeader->size; $limit = $this->chunk->getLimitMaxFileSize(); + $minimum = $this->chunk->getLimitMinimumFileSize(); $size = !is_int($size) ? null : $size; $ranges = $this->contentRangeHeader->ranges; $start = is_array($ranges) ? $ranges[0] : null; $end = is_array($ranges) ? $ranges[1] : null; - if ($limit !== null && $limit > 0 && ( + if ($limit !== null && $limit > 0 && is_int($size) && ( $size > $limit // limit size total - || $end > $limit // limit position + || is_int($end) && $end > $limit // limit position )) { throw new OutOfRangeException( sprintf( - 'Uploaded file size is bigger than allowed size: %s.', + $this->chunk->translateContext( + 'Uploaded file size is bigger than allowed size: %s.', + 'chunk-uploader' + ), Consolidation::sizeFormat($limit, 4) ) ); } + + if ($minimum !== null && is_int($size) && $size > $minimum && ( + $start !== null && $end !== null + && $minimum > ($end - $start) + && $size > ($start + $end) + )) { + throw new OutOfRangeException( + sprintf( + $this->chunk->translateContext( + 'Uploaded file size range is less than allowed minimum size: %s.', + 'chunk-uploader' + ), + Consolidation::sizeFormat($minimum, 4) + ) + ); + } + if ($this->isNewRequestId) { if ($start !== null && $start > 0) { throw new InvalidOffsetPositionException( $start, $this->contentRangeHeader->size, - 'Content-Range start bytes must be zero without Request-Id.' + $this->chunk->translateContext( + 'Content-Range start bytes must be zero without Request-Id.', + 'chunk-uploader' + ) ); } } elseif (!$this->requestIdHeader->valid) { throw new InvalidRequestId( sprintf( - 'Request id "%s" is not valid', + $this->chunk->translateContext( + 'Request id "%s" is not valid', + 'chunk-uploader' + ), $this->requestIdHeader->header ) ); @@ -190,7 +230,10 @@ public function getHandler() : ChunkHandler throw new InvalidOffsetPositionException( $start, $size, - 'Content-Range start bytes must be greater zero with Request-Id.' + $this->chunk->translateContext( + 'Content-Range start bytes must be greater zero with Request-Id.', + 'chunk-uploader' + ) ); } @@ -199,13 +242,19 @@ public function getHandler() : ChunkHandler $endSize = ($end + 1) - $start; if ($endSize < $streamSize) { throw new OutOfRangeException( - 'Uploaded file size is bigger than ending size.' + $this->chunk->translateContext( + 'Uploaded file size is bigger than ending size.', + 'chunk-uploader' + ) ); } if ($size !== null && $size < $streamSize) { throw new OutOfRangeException( - 'Range size is bigger than file size.' + $this->chunk->translateContext( + 'Range size is bigger than file size.', + 'chunk-uploader' + ) ); } diff --git a/src/Util/Filter/ContainerHelper.php b/src/Util/Filter/ContainerHelper.php index 0e13eb0..cf99c57 100644 --- a/src/Util/Filter/ContainerHelper.php +++ b/src/Util/Filter/ContainerHelper.php @@ -104,10 +104,11 @@ public static function decorate( } /** - * @param callable|array|mixed $callable + * @template T of object + * @param callable|array|class-string|mixed $callable * @param array $arguments * @param ContainerInterface|null $container - * @return array|mixed + * @return array|T|mixed * @throws Throwable */ public static function resolveCallable( diff --git a/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php b/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php index ff5c95c..de29e6d 100644 --- a/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php +++ b/src/View/ErrorRenderer/HtmlTraceAbleErrorRenderer.php @@ -84,6 +84,7 @@ protected function format( return $view->render( $path, [ + 'displayErrorDetails' => false, 'exception' => $exception ] );