From bc29eb2bb5b43cea08d9d67df966bfb5c2588b67 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 30 Mar 2023 15:47:11 +0300 Subject: [PATCH 01/11] Refactor package --- .gitattributes | 1 + .github/workflows/ci.yml | 14 +- .php-cs-fixer.dist.php | 2 + README.md | 28 +- composer.json | 10 +- docs/index.md | 367 +++++++----------- phpunit.xml.dist | 2 +- psalm-baseline.xml | 11 + psalm.xml | 11 +- src/Config/Config.php | 66 ---- src/Config/ConfigInterface.php | 22 -- src/Container/Container.php | 28 +- src/Container/ContainerInterface.php | 10 +- src/Event/AbstractTmpFileEvent.php | 22 ++ src/Event/AbstractTmpFileManagerEvent.php | 38 ++ src/Event/TmpFileCreateEvent.php | 16 - src/Event/TmpFileManagerEventArgs.php | 36 ++ src/Event/TmpFileManagerOnFinish.php | 9 + src/Event/TmpFileManagerOnStart.php | 9 + src/Event/TmpFileManagerPostCreate.php | 9 + src/Event/TmpFileManagerPostLoad.php | 9 + src/Event/TmpFileManagerPostPurge.php | 9 + src/Event/TmpFileManagerPreCreate.php | 9 + src/Event/TmpFileManagerPreLoad.php | 9 + src/Event/TmpFileManagerPrePurge.php | 9 + src/Event/TmpFileManagerPurgeEvent.php | 22 -- src/Event/TmpFileManagerStartEvent.php | 22 -- src/Event/TmpFileOnCreate.php | 9 + src/Event/TmpFileOnLoad.php | 9 + src/Event/TmpFilePostRemove.php | 9 + src/Event/TmpFilePreRemove.php | 9 + src/Event/TmpFileRemoveEvent.php | 16 - src/Filesystem/Filesystem.php | 16 +- src/Filesystem/FilesystemInterface.php | 2 +- .../DeferredPurgeCallback.php | 20 - .../DeferredPurgeHandler.php | 15 - .../DeferredPurgeHandlerInterface.php | 12 - .../AsyncGarbageCollectionHandler.php | 56 --- .../GarbageCollectionHandler.php | 44 +-- .../GarbageCollectionHandlerInterface.php | 4 +- .../Processor/AsyncProcessor.php | 46 +++ .../Processor/ProcessorInterface.php | 10 + .../Processor/SyncProcessor.php | 33 ++ .../OpenResourcesHandler.php | 35 ++ .../OpenResourcesHandlerInterface.php | 15 + .../UnclosedResourcesHandler.php | 32 -- .../UnclosedResourcesHandlerInterface.php | 12 - src/Listener/DeferredPurgeListener.php | 20 - src/Listener/GarbageCollectionListener.php | 20 - src/Listener/UnclosedResourcesListener.php | 20 - src/TmpFileManager.php | 83 ++-- src/TmpFileManagerBuilder.php | 97 +++++ src/TmpFileManagerBuilderInterface.php | 10 + src/TmpFileManagerInterface.php | 2 + tests/.gitkeep | 0 tests/Config/ConfigTest.php | 59 --- tests/Container/ContainerTest.php | 61 --- tests/Filesystem/FilesystemTest.php | 40 -- tests/Handler/DeferredPurgeCallbackTest.php | 22 -- .../Handler/GarbageCollectionHandlerTest.php | 32 -- .../Handler/UnclosedResourcesHandlerTest.php | 26 -- tests/TmpFileManagerTest.php | 51 --- tests/TmpFileTest.php | 25 -- 63 files changed, 759 insertions(+), 1013 deletions(-) create mode 100644 psalm-baseline.xml create mode 100644 src/Event/AbstractTmpFileEvent.php create mode 100644 src/Event/AbstractTmpFileManagerEvent.php delete mode 100644 src/Event/TmpFileCreateEvent.php create mode 100644 src/Event/TmpFileManagerEventArgs.php create mode 100644 src/Event/TmpFileManagerOnFinish.php create mode 100644 src/Event/TmpFileManagerOnStart.php create mode 100644 src/Event/TmpFileManagerPostCreate.php create mode 100644 src/Event/TmpFileManagerPostLoad.php create mode 100644 src/Event/TmpFileManagerPostPurge.php create mode 100644 src/Event/TmpFileManagerPreCreate.php create mode 100644 src/Event/TmpFileManagerPreLoad.php create mode 100644 src/Event/TmpFileManagerPrePurge.php delete mode 100644 src/Event/TmpFileManagerPurgeEvent.php delete mode 100644 src/Event/TmpFileManagerStartEvent.php create mode 100644 src/Event/TmpFileOnCreate.php create mode 100644 src/Event/TmpFileOnLoad.php create mode 100644 src/Event/TmpFilePostRemove.php create mode 100644 src/Event/TmpFilePreRemove.php delete mode 100644 src/Event/TmpFileRemoveEvent.php delete mode 100644 src/Handler/DeferredPurgeHandler/DeferredPurgeCallback.php delete mode 100644 src/Handler/DeferredPurgeHandler/DeferredPurgeHandler.php delete mode 100644 src/Handler/DeferredPurgeHandler/DeferredPurgeHandlerInterface.php delete mode 100644 src/Handler/GarbageCollectionHandler/AsyncGarbageCollectionHandler.php create mode 100644 src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php create mode 100644 src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php create mode 100644 src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php create mode 100644 src/Handler/OpenResourcesHandler/OpenResourcesHandler.php create mode 100644 src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php delete mode 100644 src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php delete mode 100644 src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php delete mode 100644 src/Listener/DeferredPurgeListener.php delete mode 100644 src/Listener/GarbageCollectionListener.php delete mode 100644 src/Listener/UnclosedResourcesListener.php create mode 100644 src/TmpFileManagerBuilder.php create mode 100644 src/TmpFileManagerBuilderInterface.php create mode 100644 tests/.gitkeep delete mode 100644 tests/Config/ConfigTest.php delete mode 100644 tests/Container/ContainerTest.php delete mode 100644 tests/Filesystem/FilesystemTest.php delete mode 100644 tests/Handler/DeferredPurgeCallbackTest.php delete mode 100644 tests/Handler/GarbageCollectionHandlerTest.php delete mode 100644 tests/Handler/UnclosedResourcesHandlerTest.php delete mode 100644 tests/TmpFileManagerTest.php delete mode 100644 tests/TmpFileTest.php diff --git a/.gitattributes b/.gitattributes index 29f8ea2..03576fe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,5 +10,6 @@ /composer.lock export-ignore /docs/ export-ignore /phpunit.xml.dist export-ignore +/psalm-baseline.xml export-ignore /psalm.xml export-ignore /tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65ab0d0..0ef4e19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,12 +3,18 @@ name: 'CI' on: push: paths-ignore: + - 'build/**' - 'docs/**' + - '.editorconfig' + - '.gitattributes' - 'LICENSE' - 'README.md' pull_request: paths-ignore: + - 'build/**' - 'docs/**' + - '.editorconfig' + - '.gitattributes' - 'LICENSE' - 'README.md' @@ -24,13 +30,13 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: latest + php-version: '8.1' coverage: none - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run PHPParallelLint + - name: Run ParallelLint run: composer parallel-lint -- --no-progress --ignore-fails psalm: @@ -44,7 +50,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: latest + php-version: '8.1' coverage: none - name: Install dependencies @@ -64,7 +70,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: latest + php-version: '8.1' coverage: none - name: Install dependencies diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 3d9125d..54072da 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,5 +1,7 @@ in([ __DIR__.'/src/', diff --git a/README.md b/README.md index 12a9be1..2665a79 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # TmpFileManager -[![Build Status](https://img.shields.io/github/workflow/status/denisyukphp/tmpfile-manager/build/master?style=plastic)](https://github.com/denisyukphp/tmpfile-manager/actions/workflows/ci.yml) +[![Build Status](https://img.shields.io/github/actions/workflow/status/denisyukphp/tmpfile-manager/ci.yml?branch=master&style=plastic)](https://github.com/denisyukphp/tmpfile-manager/actions/workflows/ci.yml) [![Latest Stable Version](https://img.shields.io/packagist/v/denisyukphp/tmpfile-manager?style=plastic)](https://packagist.org/packages/denisyukphp/tmpfile-manager) [![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/denisyukphp/tmpfile-manager?style=plastic&color=8892BF)](https://packagist.org/packages/denisyukphp/tmpfile-manager) [![Total Downloads](https://img.shields.io/packagist/dt/denisyukphp/tmpfile-manager?style=plastic)](https://packagist.org/packages/denisyukphp/tmpfile-manager) [![License](https://img.shields.io/packagist/l/denisyukphp/tmpfile-manager?style=plastic&color=428F7E)](https://packagist.org/packages/denisyukphp/tmpfile-manager) -Temp files manager. +Temp file manager. ## Installation @@ -20,36 +20,32 @@ This package requires PHP 8.0 or later. ## Quick usage -Configure TmpFileManager and create a temp file: +Build TmpFileManager and create a temp file: ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() +; -$tmpFileManager = new TmpFileManager($config); - -/** @var TmpFileInterface $tmpFile */ $tmpFile = $tmpFileManager->create(); ``` -All temp files which created with the manager will be purged automatically by default. +All temp files created the manager will be purged automatically by default. ## Documentation - [Default configuration](docs/index.md#default-configuration) - [Creating temp files](docs/index.md#creating-temp-files) +- [Loading temp files](docs/index.md#loading-temp-files) - [Removing temp files](docs/index.md#removing-temp-files) - [Check unclosed resources](docs/index.md#check-unclosed-resources) - [Garbage collection](docs/index.md#garbage-collection) -- [Custom handlers](docs/index.md#custom-handlers) -- [Subscribe events](docs/index.md#subscribe-events) +- [Lifecycle events](docs/index.md#lifecycle-events) Read more about temp file on [Habr](https://habr.com/ru/post/320078/). diff --git a/composer.json b/composer.json index 16ccfc9..a3de199 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "denisyukphp/tmpfile-manager", - "description": "Temp files manager.", + "description": "Temp file manager.", "keywords": [ "tmpfile", "tempnam", @@ -24,6 +24,7 @@ "require": { "php": "^8.0", "denisyukphp/tmpfile": "^3.0", + "psr/event-dispatcher": "^1.0", "symfony/event-dispatcher": "^6.0", "symfony/filesystem": "^6.0", "symfony/finder": "^6.0", @@ -52,9 +53,12 @@ "phpunit-coverage": "./vendor/bin/phpunit --verbose --colors=always --coverage-text", "phpunit-coverage-html": "./vendor/bin/phpunit --verbose --colors=always --coverage-html ./build/logs/phpunit-coverage/", "parallel-lint": "./vendor/bin/parallel-lint --colors ./src/ ./tests/", - "php-cs-fixer:fix": "./vendor/bin/php-cs-fixer fix --ansi --verbose --show-progress=dots", - "php-cs-fixer:diff": "./vendor/bin/php-cs-fixer fix --ansi --verbose --dry-run --diff", + "php-cs-fixer:fix": "./vendor/bin/php-cs-fixer fix --verbose --ansi --show-progress=dots", + "php-cs-fixer:diff": "./vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --diff", "psalm": "./vendor/bin/psalm --show-info=true", + "psalm:set-baseline": "@psalm --set-baseline=./psalm-baseline.xml", + "psalm:update-baseline": "@psalm --update-baseline", + "psalm:ignore-baseline": "@psalm --ignore-baseline", "test": [ "@parallel-lint", "@psalm", diff --git a/docs/index.md b/docs/index.md index c46bae8..399f806 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,313 +2,242 @@ - [Default configuration](#default-configuration) - [Creating temp files](#creating-temp-files) +- [Loading temp files](#loading-temp-files) - [Removing temp files](#removing-temp-files) - [Check unclosed resources](#check-unclosed-resources) - [Garbage collection](#garbage-collection) -- [Custom handlers](#custom-handlers) -- [Subscribe events](#subscribe-events) +- [Lifecycle events](#lifecycle-events) ## Default configuration -By default, temp files will purge automatically, unclosed resources check and garbage collection are off. Below is the default configuration: +By default, temp files purge automatically, check unclosed resources and garbage collection are off. Below is the default configuration: ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() +; ``` -You can use TmpFileManager with default options. +[...] ## Creating temp files -To create a temp file use `create()` method: +[...] ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() +; +``` -$tmpFileManager = new TmpFileManager(); +[...] -/** @var TmpFileInterface $tmpFile */ +```php $tmpFile = $tmpFileManager->create(); ``` -In console commands use `isolate()` method to create and handle temp files. Temp files will be immediately removed after finished callback: + +[...] ```php -$tmpFileManager->isolate(function (TmpFileInterface $tmpFile) { +$tmpFileManager->isolate(function (TmpFileInterface $tmpFile): void { // ... }); ``` -Use one of these ways to create the temp file for your tasks. +[...] -## Removing temp files +## Loading temp files -By default, created temp files will purge automatically after PHP is finished, but you can remove temp files manually with `remove()` method: +[...] ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() +; -/** @var TmpFileInterface $tmpFile */ -$tmpFileManager->create(); +$files = [ + new \SplFileInfo(__DIR__.'/cat.jpg'), + new \SplFileInfo(__DIR__.'/dog.jpg'), + new \SplFileInfo(__DIR__.'/fish.jpg'), +]; -// ... - -$tmpFileManager->remove($tmpFile); +$tmpFileManager->load(...$files); ``` -If you need to purge all temp files by force get call `purge()` method: +[...] -```php -$tmpFileManager->purge(); -``` - -All temp files will immediately remove. - -## Check unclosed resources +## Removing temp files -TmpFileManager close open resources automatically before purging temp files: +[...] ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() +; -/** @var TmpFileInterface $tmpFile */ $tmpFile = $tmpFileManager->create(); - -$fh = fopen($tmpFile->getFilename(), 'r+'); - -fwrite($fh, random_bytes(1024)); - -// ... ``` -After that you can ignore to open resources for temp files. - -## Garbage collection - -The probability is calculated by using probability/divisor, e.g. 1/100 means there is a 1% chance that the garbage collection process will start. Lifetime is seconds after which temp files will be seen as garbage and potentially cleaned up: +[...] ```php -remove($tmpFile); ``` -Also, you can start garbage collection process only with handler: +[...] ```php -$garbageCollectionHandler = new GarbageCollectionHandler(); - -$garbageCollectionHandler->handle($config); +$tmpFileManager->purge(); ``` -By default, garbage collection is off. +[...] -## Custom handlers +## Check unclosed resources -Define your handlers to get more control of temp files. Each handler implements its own interface. Below are the default handlers: +[...] ```php withOpenResourcesHandler(new OpenResourcesHandler()) + ->build() +; -use TmpFileManager\Handler\DeferredPurgeHandler\DeferredPurgeHandlerInterface; -use TmpFileManager\TmpFileManagerInterface; +$tmpFile = $tmpFileManager->create(); +$fh = fopen($tmpFile->getFilename(), 'r+'); +fwrite($fh, random_bytes(1024)); -class DeferredPurgeHandler implements DeferredPurgeHandlerInterface -{ - public function handle(TmpFileManagerInterface $tmpFileManager): void - { - // ... - } -} +// ... ``` -`UnclosedResourcesHandlerInterface::class` is handled unclosed resources of temp files: - -```php -getTmpFiles(); - - // ... - } -} -``` +## Garbage collection -`GarbageCollectionHandlerInterface::class` is handled process of garbage collection: +Garbage collection process starts after temp files purging. The probability is calculated by using probability/divisor, e. g. 1/100 means there is a 1% chance that the garbage collection process will start. Lifetime is seconds after which temp files will be seen as garbage and potentially cleaned up: ```php withGarbageCollectionHandler($garbageCollectionHandler) + ->build() +; ``` -Use handlers to pass specific logic or rewrite currents. - -## Subscribe events +Choose sync or async processor to make memory consumption more efficient: -Subscribe to events to inject your code in lifecycle of temp files: - -```php -addListener(TmpFileManagerStartEvent::class, function (TmpFileManagerStartEvent $event): void { - /** @var TmpFileManagerInterface $tmpFileManager */ - $tmpFileManager = $event->tmpFileManage; - /** @var ConfigInterface $config */ - $config = $event->config; - /** @var ContainerInterface $container */ - $container = $event->container; - /** @var FilesystemInterface $filesystem */ - $filesystem = $event->filesystem; - - // ... -}); -``` - -`TmpFileCreateEvent::class` is fired after a temp file created: - -```php -$eventDispatcher->addListener(TmpFileCreateEvent::class, function (TmpFileCreateEvent $event): void { - /** @var TmpFileInterface $tmpFile */ - $tmpFile = $event->tmpFile; - - // ... -}); -``` +By default, garbage collection is off. -`TmpFileRemoveEvent::class` is fired before the temp file removed: +## Lifecycle events + +Temp file manager is based on events. All handlers implemented through events. Use them to put your own code in temp file's lifecycle: + +```text + ┌─────────────┐ + │ onStart │ + └──────┬──────┘ + ┌─────────┴─────────┐ + ▼ ▼ +┌─────────────┐ ┌─────────────┐ +│ preCreate │ │ preLoad │ +│ onCreate │ │ onLoad │ +│ postCreate │ │ postLoad │ +└──────┬──────┘ └──────┬──────┘ + └─────────┬─────────┘ + ▼ + ┌─────────────┐ + │ preRemove │ + │ postRemove │ + └──────┬──────┘ + ▼ + ┌─────────────┐ + │ prePurge │ + │ postPurge │ + └──────┬──────┘ + ▼ + ┌─────────────┐ + │ onFinish │ + └─────────────┘ +``` + +Events related to temp file manager: + +- [TmpFileManagerOnStart](../src/Event/TmpFileManagerOnStart.php) +- [TmpFileManagerPreCreate](../src/Event/TmpFileManagerPreCreate.php) +- [TmpFileManagerPostCreate](../src/Event/TmpFileManagerPostCreate.php) +- [TmpFileManagerPreLoad](../src/Event/TmpFileManagerPreLoad.php) +- [TmpFileManagerPostLoad](../src/Event/TmpFileManagerPostLoad.php) +- [TmpFileManagerPrePurge](../src/Event/TmpFileManagerPrePurge.php) +- [TmpFileManagerPostPurge](../src/Event/TmpFileManagerPostPurge.php) +- [TmpFileManagerOnFinish](../src/Event/TmpFileManagerOnFinish.php) + +Events related to temp file: + +- [TmpFileOnCreate](../src/Event/TmpFileOnCreate.php) +- [TmpFileOnLoad](../src/Event/TmpFileOnLoad.php) +- [TmpFilePreRemove](../src/Event/TmpFilePreRemove.php) +- [TmpFilePostRemove](../src/Event/TmpFilePostRemove.php) + +[...] ```php -$eventDispatcher->addListener(TmpFileRemoveEvent::class, function (TmpFileRemoveEvent $event): void { - /** @var TmpFileInterface $tmpFile */ - $tmpFile = $event->tmpFile; - - // ... -}); -``` +addListener(TmpFileManagerPurgeEvent::class, function (TmpFileManagerPurgeEvent $event): void { - /** @var TmpFileManagerInterface $tmpFileManager */ - $tmpFileManager = $event->tmpFileManage; - /** @var ConfigInterface $config */ - $config = $event->config; - /** @var ContainerInterface $container */ - $container = $event->container; - /** @var FilesystemInterface $filesystem */ - $filesystem = $event->filesystem; - - // ... -}); -``` - -After that you need to add event dispatcher to TmpFileManager to your event listeners run fire. +$tmpFileManager = (new TmpFileManagerBuilder()) + ->addEventListener(TmpFileOnCreate::class, static function (TmpFileOnCreate $tmpFileOnCreate): void { + // ... + }) + ->build() +; -```php -$tmpFileManager = new TmpFileManager( - eventDispatcher: $eventDispatcher, -); +$tmpFile = $tmpFileManager->create(); ``` -This allows flexible management. +[...] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ec0d468..90978c3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,4 +1,4 @@ - + + + + + iterator_to_array($this->tmpFiles, false) + + + TmpFileInterface[] + + + diff --git a/psalm.xml b/psalm.xml index 6c68162..d194b73 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,19 +1,20 @@ - + - - + + - + - + diff --git a/src/Config/Config.php b/src/Config/Config.php index 7afc999..9396992 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -4,36 +4,15 @@ namespace TmpFileManager\Config; -use TmpFileManager\Handler\DeferredPurgeHandler\DeferredPurgeHandler; -use TmpFileManager\Handler\DeferredPurgeHandler\DeferredPurgeHandlerInterface; -use TmpFileManager\Handler\GarbageCollectionHandler\GarbageCollectionHandler; -use TmpFileManager\Handler\GarbageCollectionHandler\GarbageCollectionHandlerInterface; -use TmpFileManager\Handler\UnclosedResourcesHandler\UnclosedResourcesHandler; -use TmpFileManager\Handler\UnclosedResourcesHandler\UnclosedResourcesHandlerInterface; - final class Config implements ConfigInterface { private string $tmpFileDirectory; - private DeferredPurgeHandlerInterface $deferredPurgeHandler; - private UnclosedResourcesHandlerInterface $unclosedResourcesHandler; - private GarbageCollectionHandlerInterface $garbageCollectionHandler; public function __construct( ?string $tmpFileDirectory = null, private string $tmpFilePrefix = 'php', - private bool $isDeferredPurge = true, - ?DeferredPurgeHandlerInterface $deferredPurgeHandler = null, - private bool $isUnclosedResourcesCheck = false, - ?UnclosedResourcesHandlerInterface $unclosedResourcesHandler = null, - private int $garbageCollectionProbability = 0, - private int $garbageCollectionDivisor = 100, - private int $garbageCollectionLifetime = 3600, - ?GarbageCollectionHandlerInterface $garbageCollectionHandler = null, ) { $this->tmpFileDirectory = $tmpFileDirectory ?? sys_get_temp_dir(); - $this->deferredPurgeHandler = $deferredPurgeHandler ?? new DeferredPurgeHandler(); - $this->unclosedResourcesHandler = $unclosedResourcesHandler ?? new UnclosedResourcesHandler(); - $this->garbageCollectionHandler = $garbageCollectionHandler ?? new GarbageCollectionHandler(); } public function getTmpFileDirectory(): string @@ -45,49 +24,4 @@ public function getTmpFilePrefix(): string { return $this->tmpFilePrefix; } - - public function isDeferredPurge(): bool - { - return $this->isDeferredPurge; - } - - public function getDeferredPurgeHandler(): DeferredPurgeHandlerInterface - { - return $this->deferredPurgeHandler; - } - - public function isUnclosedResourcesCheck(): bool - { - return $this->isUnclosedResourcesCheck; - } - - public function getUnclosedResourcesHandler(): UnclosedResourcesHandlerInterface - { - return $this->unclosedResourcesHandler; - } - - public function getGarbageCollectionProbability(): int - { - return $this->garbageCollectionProbability; - } - - public function getGarbageCollectionDivisor(): int - { - return $this->garbageCollectionDivisor; - } - - public function getGarbageCollectionLifetime(): int - { - return $this->garbageCollectionLifetime; - } - - public function isGarbageCollection(): bool - { - return 0 < $this->garbageCollectionProbability; - } - - public function getGarbageCollectionHandler(): GarbageCollectionHandlerInterface - { - return $this->garbageCollectionHandler; - } } diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 34686fe..877e623 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -4,31 +4,9 @@ namespace TmpFileManager\Config; -use TmpFileManager\Handler\DeferredPurgeHandler\DeferredPurgeHandlerInterface; -use TmpFileManager\Handler\GarbageCollectionHandler\GarbageCollectionHandlerInterface; -use TmpFileManager\Handler\UnclosedResourcesHandler\UnclosedResourcesHandlerInterface; - interface ConfigInterface { public function getTmpFileDirectory(): string; public function getTmpFilePrefix(): string; - - public function isDeferredPurge(): bool; - - public function getDeferredPurgeHandler(): DeferredPurgeHandlerInterface; - - public function isUnclosedResourcesCheck(): bool; - - public function getUnclosedResourcesHandler(): UnclosedResourcesHandlerInterface; - - public function getGarbageCollectionProbability(): int; - - public function getGarbageCollectionDivisor(): int; - - public function getGarbageCollectionLifetime(): int; - - public function isGarbageCollection(): bool; - - public function getGarbageCollectionHandler(): GarbageCollectionHandlerInterface; } diff --git a/src/Container/Container.php b/src/Container/Container.php index 817e30f..276443b 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -17,6 +17,10 @@ public function __construct() public function addTmpFile(TmpFileInterface $tmpFile): void { + if ($this->hasTmpFile($tmpFile)) { + throw new \InvalidArgumentException(sprintf('Temp file "%s" has been already added.', $tmpFile->getFilename())); + } + $this->tmpFiles->attach($tmpFile); } @@ -27,22 +31,32 @@ public function hasTmpFile(TmpFileInterface $tmpFile): bool public function removeTmpFile(TmpFileInterface $tmpFile): void { + if (!$this->hasTmpFile($tmpFile)) { + throw new \InvalidArgumentException(sprintf('Temp file "%s" hasn\'t been added yet.', $tmpFile->getFilename())); + } + $this->tmpFiles->detach($tmpFile); } + public function removeAll(): void + { + $this->tmpFiles->removeAll($this->tmpFiles); + } + /** - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement - * - * @return list + * @return TmpFileInterface[] */ - public function getTmpFiles(): array + public function toArray(): array { + if (0 === $this->count()) { + return []; + } + return iterator_to_array($this->tmpFiles, false); } - public function getTmpFilesCount(): int + public function count(): int { - return $this->tmpFiles->count(); + return \count($this->tmpFiles); } } diff --git a/src/Container/ContainerInterface.php b/src/Container/ContainerInterface.php index fdd53cc..70a058a 100644 --- a/src/Container/ContainerInterface.php +++ b/src/Container/ContainerInterface.php @@ -6,7 +6,7 @@ use TmpFile\TmpFileInterface; -interface ContainerInterface +interface ContainerInterface extends \Countable { public function addTmpFile(TmpFileInterface $tmpFile): void; @@ -14,10 +14,10 @@ public function hasTmpFile(TmpFileInterface $tmpFile): bool; public function removeTmpFile(TmpFileInterface $tmpFile): void; + public function removeAll(): void; + /** - * @return list + * @return TmpFileInterface[] */ - public function getTmpFiles(): array; - - public function getTmpFilesCount(): int; + public function toArray(): array; } diff --git a/src/Event/AbstractTmpFileEvent.php b/src/Event/AbstractTmpFileEvent.php new file mode 100644 index 0000000..86f8242 --- /dev/null +++ b/src/Event/AbstractTmpFileEvent.php @@ -0,0 +1,22 @@ +tmpFile = $tmpFile; + } + + public function getTmpFile(): TmpFileInterface + { + return $this->tmpFile; + } +} diff --git a/src/Event/AbstractTmpFileManagerEvent.php b/src/Event/AbstractTmpFileManagerEvent.php new file mode 100644 index 0000000..2b42db8 --- /dev/null +++ b/src/Event/AbstractTmpFileManagerEvent.php @@ -0,0 +1,38 @@ +config = $fileManagerEventArgs->getConfig(); + $this->container = $fileManagerEventArgs->getContainer(); + $this->filesystem = $fileManagerEventArgs->getFilesystem(); + } + + public function getConfig(): ConfigInterface + { + return $this->config; + } + + public function getContainer(): ContainerInterface + { + return $this->container; + } + + public function getFilesystem(): FilesystemInterface + { + return $this->filesystem; + } +} diff --git a/src/Event/TmpFileCreateEvent.php b/src/Event/TmpFileCreateEvent.php deleted file mode 100644 index e0119a9..0000000 --- a/src/Event/TmpFileCreateEvent.php +++ /dev/null @@ -1,16 +0,0 @@ -config = $config; + $this->container = $container; + $this->filesystem = $filesystem; + } + + public function getConfig(): ConfigInterface + { + return $this->config; + } + + public function getContainer(): ContainerInterface + { + return $this->container; + } + + public function getFilesystem(): FilesystemInterface + { + return $this->filesystem; + } +} diff --git a/src/Event/TmpFileManagerOnFinish.php b/src/Event/TmpFileManagerOnFinish.php new file mode 100644 index 0000000..7b51eac --- /dev/null +++ b/src/Event/TmpFileManagerOnFinish.php @@ -0,0 +1,9 @@ +fs = $fs ?? new Fs(); + $this->symfonyFilesystem = $symfonyFilesystem ?? new SymfonyFilesystem(); } - public function getTmpFileName(string $dir, string $prefix): string + public function createTmpFile(string $tmpFileDirectory, string $tmpFilePrefix): string { - return $this->fs->tempnam($dir, $prefix); + return $this->symfonyFilesystem->tempnam($tmpFileDirectory, $tmpFilePrefix); } public function existsTmpFile(TmpFileInterface $tmpFile): bool { - return $this->fs->exists($tmpFile->getFilename()); + return $this->symfonyFilesystem->exists($tmpFile->getFilename()); } public function removeTmpFile(TmpFileInterface $tmpFile): void { - $this->fs->remove($tmpFile->getFilename()); + $this->symfonyFilesystem->remove($tmpFile->getFilename()); } } diff --git a/src/Filesystem/FilesystemInterface.php b/src/Filesystem/FilesystemInterface.php index d60f910..98757b1 100644 --- a/src/Filesystem/FilesystemInterface.php +++ b/src/Filesystem/FilesystemInterface.php @@ -8,7 +8,7 @@ interface FilesystemInterface { - public function getTmpFileName(string $dir, string $prefix): string; + public function createTmpFile(string $tmpFileDirectory, string $tmpFilePrefix): string; public function existsTmpFile(TmpFileInterface $tmpFile): bool; diff --git a/src/Handler/DeferredPurgeHandler/DeferredPurgeCallback.php b/src/Handler/DeferredPurgeHandler/DeferredPurgeCallback.php deleted file mode 100644 index e545fb9..0000000 --- a/src/Handler/DeferredPurgeHandler/DeferredPurgeCallback.php +++ /dev/null @@ -1,20 +0,0 @@ -tmpFileManager->purge(); - } -} diff --git a/src/Handler/DeferredPurgeHandler/DeferredPurgeHandler.php b/src/Handler/DeferredPurgeHandler/DeferredPurgeHandler.php deleted file mode 100644 index 70d5bc5..0000000 --- a/src/Handler/DeferredPurgeHandler/DeferredPurgeHandler.php +++ /dev/null @@ -1,15 +0,0 @@ -find('find', '/usr/bin/find', $extraDirs)) { - throw new \RuntimeException("Util \"find\" isn't supporting."); - } - - $this->command = $command; - } - - public function handle(ConfigInterface $config): void - { - $tmpFileDir = $config->getTmpFileDirectory(); - $tmpFilePrefix = $config->getTmpFilePrefix(); - $gcProbability = $config->getGarbageCollectionProbability(); - $gcDivisor = $config->getGarbageCollectionDivisor(); - $gcLifetime = $config->getGarbageCollectionLifetime(); - - if (0 === $gcProbability || mt_rand(1, $gcDivisor) > $gcProbability) { - return; - } - - $process = new Process([ - $this->command, $tmpFileDir, - '-name', $tmpFilePrefix.'*', - '-type', 'f', - '-amin', '+'.ceil($gcLifetime / 60), - '-maxdepth', 1, - '-delete', - ]); - - $process->setOptions([ - 'create_new_console' => true, - ]); - - $process->start(); - } -} diff --git a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php index d2f7ea5..588c9e3 100644 --- a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php +++ b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php @@ -4,43 +4,27 @@ namespace TmpFileManager\Handler\GarbageCollectionHandler; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Finder\Finder; -use TmpFileManager\Config\ConfigInterface; +use TmpFileManager\Handler\GarbageCollectionHandler\Processor\ProcessorInterface; final class GarbageCollectionHandler implements GarbageCollectionHandlerInterface { - private Filesystem $fs; - - public function __construct(?Filesystem $fs = null) - { - $this->fs = $fs ?? new Filesystem(); + public function __construct( + private int $probability, + private int $divisor, + private int $lifetime, + private ProcessorInterface $processor, + ) { } - public function handle(ConfigInterface $config): void + public function handle(string $tmpFileDirectory, string $tmpFilePrefix): void { - $tmpFileDir = $config->getTmpFileDirectory(); - $tmpFilePrefix = $config->getTmpFilePrefix(); - $gcProbability = $config->getGarbageCollectionProbability(); - $gcDivisor = $config->getGarbageCollectionDivisor(); - $gcLifetime = $config->getGarbageCollectionLifetime(); - - if (0 === $gcProbability || mt_rand(1, $gcDivisor) > $gcProbability) { - return; - } - - $finder = (new Finder()) - ->in($tmpFileDir) - ->name($tmpFilePrefix.'*') - ->depth('== 0') - ->date('< '.date('Y-m-d H:i:s', time() - $gcLifetime)) - ->files() - ; - - if (!$finder->hasResults()) { - return; + if ($this->isChance($this->probability, $this->divisor)) { + $this->processor->process($tmpFileDirectory, $tmpFilePrefix, $this->lifetime); } + } - $this->fs->remove($finder->getIterator()); + private function isChance(int $probability, int $divisor): bool + { + return 0 !== $probability && mt_rand(1, $divisor) > $probability; } } diff --git a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php index b44e840..8f80425 100644 --- a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php +++ b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php @@ -4,9 +4,7 @@ namespace TmpFileManager\Handler\GarbageCollectionHandler; -use TmpFileManager\Config\ConfigInterface; - interface GarbageCollectionHandlerInterface { - public function handle(ConfigInterface $config): void; + public function handle(string $tmpFileDirectory, string $tmpFilePrefix): void; } diff --git a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php new file mode 100644 index 0000000..37ad978 --- /dev/null +++ b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php @@ -0,0 +1,46 @@ +find('find', '/usr/bin/find', $extraCommandDirectories); + + if (null === $command) { + throw new \RuntimeException('Util "find" isn\'t supporting.'); + } + + $this->command = $command; + } + + public function process(string $tmpFileDirectory, string $tmpFilePrefix, int $tmpFileLifetimeInSeconds): void + { + $process = new Process([ + $this->command, + $tmpFileDirectory, + '-name', $tmpFilePrefix.'*', + '-type', 'f', + '-amin', '+'.ceil($tmpFileLifetimeInSeconds / 60), + '-maxdepth', 1, + '-delete', + ]); + + $process->setOptions([ + 'create_new_console' => true, + ]); + + $process->start(); + } +} diff --git a/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php b/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php new file mode 100644 index 0000000..e2e5d98 --- /dev/null +++ b/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php @@ -0,0 +1,10 @@ +filesystem = $filesystem ?? new Filesystem(); + } + + public function process(string $tmpFileDirectory, string $tmpFilePrefix, int $tmpFileLifetimeInSeconds): void + { + $finder = (new Finder()) + ->in($tmpFileDirectory) + ->name($tmpFilePrefix.'*') + ->depth('== 0') + ->date('< '.date('Y-m-d H:i:s', time() - $tmpFileLifetimeInSeconds)) + ->files() + ; + + if ($finder->hasResults()) { + $this->filesystem->remove($finder->getIterator()); + } + } +} diff --git a/src/Handler/OpenResourcesHandler/OpenResourcesHandler.php b/src/Handler/OpenResourcesHandler/OpenResourcesHandler.php new file mode 100644 index 0000000..b6d715b --- /dev/null +++ b/src/Handler/OpenResourcesHandler/OpenResourcesHandler.php @@ -0,0 +1,35 @@ + $tmpFile->getFilename(), $tmpFiles); + + foreach ($resources as $resource) { + if (!stream_is_local($resource)) { + continue; // @codeCoverageIgnore + } + + $metadata = stream_get_meta_data($resource); + + if (\in_array($metadata['uri'], $filenames, true)) { + fclose($resource); + } + } + } +} diff --git a/src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php b/src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php new file mode 100644 index 0000000..300629c --- /dev/null +++ b/src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php @@ -0,0 +1,15 @@ +getFilename(); - }, $container->getTmpFiles()); - - foreach (get_resources('stream') as $resource) { - if (!stream_is_local($resource)) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - - $metadata = stream_get_meta_data($resource); - - if (\in_array($metadata['uri'], $filenames, true)) { - fclose($resource); - } - } - } -} diff --git a/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php b/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php deleted file mode 100644 index 16c3121..0000000 --- a/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -config->isDeferredPurge()) { - $event->config->getDeferredPurgeHandler()->handle($event->tmpFileManager); - } - } -} diff --git a/src/Listener/GarbageCollectionListener.php b/src/Listener/GarbageCollectionListener.php deleted file mode 100644 index 1772417..0000000 --- a/src/Listener/GarbageCollectionListener.php +++ /dev/null @@ -1,20 +0,0 @@ -config->isGarbageCollection()) { - $event->config->getGarbageCollectionHandler()->handle($event->config); - } - } -} diff --git a/src/Listener/UnclosedResourcesListener.php b/src/Listener/UnclosedResourcesListener.php deleted file mode 100644 index c691354..0000000 --- a/src/Listener/UnclosedResourcesListener.php +++ /dev/null @@ -1,20 +0,0 @@ -config->isUnclosedResourcesCheck()) { - $event->config->getUnclosedResourcesHandler()->handle($event->container); - } - } -} diff --git a/src/TmpFileManager.php b/src/TmpFileManager.php index e83a8b5..8c351ea 100644 --- a/src/TmpFileManager.php +++ b/src/TmpFileManager.php @@ -4,22 +4,28 @@ namespace TmpFileManager; +use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use TmpFile\TmpFileInterface; use TmpFileManager\Config\Config; use TmpFileManager\Config\ConfigInterface; use TmpFileManager\Container\Container; use TmpFileManager\Container\ContainerInterface; -use TmpFileManager\Event\TmpFileCreateEvent; -use TmpFileManager\Event\TmpFileManagerPurgeEvent; -use TmpFileManager\Event\TmpFileManagerStartEvent; -use TmpFileManager\Event\TmpFileRemoveEvent; +use TmpFileManager\Event\TmpFileManagerEventArgs; +use TmpFileManager\Event\TmpFileManagerOnFinish; +use TmpFileManager\Event\TmpFileManagerOnStart; +use TmpFileManager\Event\TmpFileManagerPostCreate; +use TmpFileManager\Event\TmpFileManagerPostLoad; +use TmpFileManager\Event\TmpFileManagerPostPurge; +use TmpFileManager\Event\TmpFileManagerPreCreate; +use TmpFileManager\Event\TmpFileManagerPreLoad; +use TmpFileManager\Event\TmpFileManagerPrePurge; +use TmpFileManager\Event\TmpFileOnCreate; +use TmpFileManager\Event\TmpFileOnLoad; +use TmpFileManager\Event\TmpFilePostRemove; +use TmpFileManager\Event\TmpFilePreRemove; use TmpFileManager\Filesystem\Filesystem; use TmpFileManager\Filesystem\FilesystemInterface; -use TmpFileManager\Listener\DeferredPurgeListener; -use TmpFileManager\Listener\GarbageCollectionListener; -use TmpFileManager\Listener\UnclosedResourcesListener; final class TmpFileManager implements TmpFileManagerInterface { @@ -38,23 +44,41 @@ public function __construct( $this->container = $container ?? new Container(); $this->filesystem = $filesystem ?? new Filesystem(); $this->eventDispatcher = $eventDispatcher ?? new EventDispatcher(); - - $this->eventDispatcher->addListener(TmpFileManagerStartEvent::class, new GarbageCollectionListener()); - $this->eventDispatcher->addListener(TmpFileManagerStartEvent::class, new DeferredPurgeListener()); - $this->eventDispatcher->addListener(TmpFileManagerPurgeEvent::class, new UnclosedResourcesListener()); - $this->eventDispatcher->dispatch(new TmpFileManagerStartEvent($this, $this->config, $this->container, $this->filesystem)); + $this->eventDispatcher->dispatch(new TmpFileManagerOnStart($this->getTmpFileManagerEventArgs())); + register_shutdown_function([$this, 'purge']); } public function create(): TmpFileInterface { - $filename = $this->filesystem->getTmpFileName($this->config->getTmpFileDirectory(), $this->config->getTmpFilePrefix()); - $tmpFile = new TmpFile($filename); + $this->eventDispatcher->dispatch(new TmpFileManagerPreCreate($this->getTmpFileManagerEventArgs())); + $tmpFileDirectory = $this->config->getTmpFileDirectory(); + $tmpFilePrefix = $this->config->getTmpFilePrefix(); + $tmpFile = new TmpFile($this->filesystem->createTmpFile($tmpFileDirectory, $tmpFilePrefix)); $this->container->addTmpFile($tmpFile); - $this->eventDispatcher->dispatch(new TmpFileCreateEvent($tmpFile)); + $this->eventDispatcher->dispatch(new TmpFileOnCreate($tmpFile)); + $this->eventDispatcher->dispatch(new TmpFileManagerPostCreate($this->getTmpFileManagerEventArgs())); return $tmpFile; } + public function load(\SplFileInfo ...$files): void + { + $this->eventDispatcher->dispatch(new TmpFileManagerPreLoad($this->getTmpFileManagerEventArgs())); + + foreach ($files as $file) { + $tmpFile = new TmpFile($file->getPathname()); + $this->eventDispatcher->dispatch(new TmpFileOnLoad($tmpFile)); + + if (!$this->filesystem->existsTmpFile($tmpFile)) { + throw new \InvalidArgumentException(sprintf('Temp file "%s" doesn\'t exist.', $tmpFile->getFilename())); + } + + $this->container->addTmpFile($tmpFile); + } + + $this->eventDispatcher->dispatch(new TmpFileManagerPostLoad($this->getTmpFileManagerEventArgs())); + } + public function isolate(callable $callback): void { $tmpFile = $this->create(); @@ -68,24 +92,35 @@ public function isolate(callable $callback): void public function remove(TmpFileInterface $tmpFile): void { - $this->eventDispatcher->dispatch(new TmpFileRemoveEvent($tmpFile)); - - if ($this->container->hasTmpFile($tmpFile)) { - $this->container->removeTmpFile($tmpFile); + if (!$this->filesystem->existsTmpFile($tmpFile)) { + throw new \InvalidArgumentException(sprintf('Temp file "%s" has been already removed.', $tmpFile->getFilename())); } - if ($this->filesystem->existsTmpFile($tmpFile)) { - $this->filesystem->removeTmpFile($tmpFile); + if (!$this->container->hasTmpFile($tmpFile)) { + throw new \InvalidArgumentException(sprintf('Temp file "%s" wasn\'t create through temp file manager.', $tmpFile->getFilename())); } + + $this->eventDispatcher->dispatch(new TmpFilePreRemove($tmpFile)); + $this->container->removeTmpFile($tmpFile); + $this->filesystem->removeTmpFile($tmpFile); + $this->eventDispatcher->dispatch(new TmpFilePostRemove($tmpFile)); } public function purge(): void { - $this->eventDispatcher->dispatch(new TmpFileManagerPurgeEvent($this, $this->config, $this->container, $this->filesystem)); - $tmpFiles = $this->container->getTmpFiles(); + $this->eventDispatcher->dispatch(new TmpFileManagerPrePurge($this->getTmpFileManagerEventArgs())); + $tmpFiles = $this->container->toArray(); foreach ($tmpFiles as $tmpFile) { $this->remove($tmpFile); } + + $this->eventDispatcher->dispatch(new TmpFileManagerPostPurge($this->getTmpFileManagerEventArgs())); + $this->eventDispatcher->dispatch(new TmpFileManagerOnFinish($this->getTmpFileManagerEventArgs())); + } + + private function getTmpFileManagerEventArgs(): TmpFileManagerEventArgs + { + return new TmpFileManagerEventArgs($this->config, $this->container, $this->filesystem); } } diff --git a/src/TmpFileManagerBuilder.php b/src/TmpFileManagerBuilder.php new file mode 100644 index 0000000..b010916 --- /dev/null +++ b/src/TmpFileManagerBuilder.php @@ -0,0 +1,97 @@ +symfonyFilesystem = $symfonyFilesystem ?? new SymfonyFilesystem(); + $this->eventDispatcher = $eventDispatcher ?? new EventDispatcher(); + } + + public function withTmpFileDirectory(string $tmpFileDirectory): self + { + $self = clone $this; + $self->tmpFileDirectory = $tmpFileDirectory; + + return $self; + } + + public function withTmpFilePrefix(string $tmpFilePrefix): self + { + $self = clone $this; + $self->tmpFilePrefix = $tmpFilePrefix; + + return $self; + } + + public function withOpenResourcesHandler(OpenResourcesHandlerInterface $openResourcesHandler): self + { + $self = clone $this; + $self->addEventListener( + TmpFileManagerPrePurge::class, + static function (TmpFileManagerPrePurge $tmpFileManagerPrePurge) use ($openResourcesHandler): void { + $openResourcesHandler->handle( + tmpFiles: $tmpFileManagerPrePurge->getContainer()->toArray(), + ); + }, + ); + + return $self; + } + + public function withGarbageCollectionHandler(GarbageCollectionHandlerInterface $garbageCollectionHandler): self + { + $self = clone $this; + $self->addEventListener( + TmpFileManagerPostPurge::class, + static function (TmpFileManagerPostPurge $tmpFileManagerPostPurge) use ($garbageCollectionHandler): void { + $garbageCollectionHandler->handle( + tmpFileDirectory: $tmpFileManagerPostPurge->getConfig()->getTmpFileDirectory(), + tmpFilePrefix: $tmpFileManagerPostPurge->getConfig()->getTmpFilePrefix(), + ); + }, + ); + + return $self; + } + + public function addEventListener(string $eventName, callable $listenerCallback): self + { + $self = clone $this; + $self->eventDispatcher->addListener($eventName, $listenerCallback); + + return $self; + } + + public function build(): TmpFileManagerInterface + { + return new TmpFileManager( + config: new Config($this->tmpFileDirectory, $this->tmpFilePrefix), + container: new Container(), + filesystem: new Filesystem($this->symfonyFilesystem), + eventDispatcher: $this->eventDispatcher, + ); + } +} diff --git a/src/TmpFileManagerBuilderInterface.php b/src/TmpFileManagerBuilderInterface.php new file mode 100644 index 0000000..42d9bed --- /dev/null +++ b/src/TmpFileManagerBuilderInterface.php @@ -0,0 +1,10 @@ +assertSame(sys_get_temp_dir(), $config->getTmpFileDirectory()); - $this->assertSame('php', $config->getTmpFilePrefix()); - $this->assertTrue($config->isDeferredPurge()); - $this->assertInstanceOf(DeferredPurgeHandler::class, $config->getDeferredPurgeHandler()); - $this->assertFalse($config->isUnclosedResourcesCheck()); - $this->assertInstanceOf(UnclosedResourcesHandler::class, $config->getUnclosedResourcesHandler()); - $this->assertSame(0, $config->getGarbageCollectionProbability()); - $this->assertSame(100, $config->getGarbageCollectionDivisor()); - $this->assertSame(3600, $config->getGarbageCollectionLifetime()); - $this->assertFalse($config->isGarbageCollection()); - $this->assertInstanceOf(GarbageCollectionHandler::class, $config->getGarbageCollectionHandler()); - } - - public function testCustomOptions(): void - { - $config = new Config( - tmpFileDirectory: sys_get_temp_dir(), - tmpFilePrefix: 'php', - isDeferredPurge: true, - deferredPurgeHandler: new DeferredPurgeHandler(), - isUnclosedResourcesCheck: false, - unclosedResourcesHandler: new UnclosedResourcesHandler(), - garbageCollectionProbability: 0, - garbageCollectionDivisor: 100, - garbageCollectionLifetime: 3600, - garbageCollectionHandler: new GarbageCollectionHandler(), - ); - - $this->assertSame(sys_get_temp_dir(), $config->getTmpFileDirectory()); - $this->assertSame('php', $config->getTmpFilePrefix()); - $this->assertTrue($config->isDeferredPurge()); - $this->assertInstanceOf(DeferredPurgeHandler::class, $config->getDeferredPurgeHandler()); - $this->assertFalse($config->isUnclosedResourcesCheck()); - $this->assertInstanceOf(UnclosedResourcesHandler::class, $config->getUnclosedResourcesHandler()); - $this->assertSame(0, $config->getGarbageCollectionProbability()); - $this->assertSame(100, $config->getGarbageCollectionDivisor()); - $this->assertSame(3600, $config->getGarbageCollectionLifetime()); - $this->assertFalse($config->isGarbageCollection()); - $this->assertInstanceOf(GarbageCollectionHandler::class, $config->getGarbageCollectionHandler()); - } -} diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php deleted file mode 100644 index d8b5d0e..0000000 --- a/tests/Container/ContainerTest.php +++ /dev/null @@ -1,61 +0,0 @@ -addTmpFile($tmpFileManager->create()); - - $this->assertCount(1, $container->getTmpFiles()); - } - - public function testHasTmpFile(): void - { - $tmpFileManager = new TmpFileManager(); - $container = new Container(); - $tmpFile = $tmpFileManager->create(); - $container->addTmpFile($tmpFile); - - $this->assertTrue($container->hasTmpFile($tmpFile)); - } - - public function testRemoveTmpFile(): void - { - $tmpFileManager = new TmpFileManager(); - $container = new Container(); - $tmpFile = $tmpFileManager->create(); - $container->addTmpFile($tmpFile); - $container->removeTmpFile($tmpFile); - - $this->assertCount(0, $container->getTmpFiles()); - } - - public function testGetTmpFiles(): void - { - $tmpFileManager = new TmpFileManager(); - $container = new Container(); - $container->addTmpFile($tmpFileManager->create()); - $tmpFiles = $container->getTmpFiles(); - - $this->assertNotEmpty($tmpFiles); - } - - public function testGetTmpFilesCount(): void - { - $manager = new TmpFileManager(); - $container = new Container(); - $container->addTmpFile($manager->create()); - - $this->assertEquals(1, $container->getTmpFilesCount()); - } -} diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php deleted file mode 100644 index 7ddfa71..0000000 --- a/tests/Filesystem/FilesystemTest.php +++ /dev/null @@ -1,40 +0,0 @@ -getTmpFileName(sys_get_temp_dir(), 'php'); - $this->assertFileExists($filename); - - unlink($filename); - } - - public function testExistsTmpFile(): void - { - $filesystem = new Filesystem(); - $tmpFileManager = new TmpFileManager(); - $tmpFile = $tmpFileManager->create(); - - $this->assertTrue($filesystem->existsTmpFile($tmpFile)); - } - - public function testRemoveTmpFile(): void - { - $filesystem = new Filesystem(); - $tmpFileManager = new TmpFileManager(); - $tmpFile = $tmpFileManager->create(); - $filesystem->removeTmpFile($tmpFile); - - $this->assertFileDoesNotExist($tmpFile->getFilename()); - } -} diff --git a/tests/Handler/DeferredPurgeCallbackTest.php b/tests/Handler/DeferredPurgeCallbackTest.php deleted file mode 100644 index 8508d97..0000000 --- a/tests/Handler/DeferredPurgeCallbackTest.php +++ /dev/null @@ -1,22 +0,0 @@ -create(); - $deferredPurgeCallback(); - - $this->assertFileDoesNotExist($tmpFile->getFilename()); - } -} diff --git a/tests/Handler/GarbageCollectionHandlerTest.php b/tests/Handler/GarbageCollectionHandlerTest.php deleted file mode 100644 index 934e7fd..0000000 --- a/tests/Handler/GarbageCollectionHandlerTest.php +++ /dev/null @@ -1,32 +0,0 @@ -create(); - $fs->touch($tmpFile->getFilename(), time() - 3600); - - $config = new Config( - garbageCollectionProbability: 100, - garbageCollectionLifetime: 0, - ); - - $garbageCollectionHandler = new GarbageCollectionHandler(); - $garbageCollectionHandler->handle($config); - - $this->assertFileDoesNotExist($tmpFile->getFilename()); - } -} diff --git a/tests/Handler/UnclosedResourcesHandlerTest.php b/tests/Handler/UnclosedResourcesHandlerTest.php deleted file mode 100644 index 74419df..0000000 --- a/tests/Handler/UnclosedResourcesHandlerTest.php +++ /dev/null @@ -1,26 +0,0 @@ -create(); - $container->addTmpFile($tmpFile); - $fh = fopen($tmpFile->getFilename(), 'r'); - $unclosedResourcesHandler = new UnclosedResourcesHandler(); - $unclosedResourcesHandler->handle($container); - - $this->assertFalse(\is_resource($fh)); - } -} diff --git a/tests/TmpFileManagerTest.php b/tests/TmpFileManagerTest.php deleted file mode 100644 index e797949..0000000 --- a/tests/TmpFileManagerTest.php +++ /dev/null @@ -1,51 +0,0 @@ -create(); - - $this->assertFileExists($tmpFile->getFilename()); - } - - public function testIsolate(): void - { - $container = new Container(); - $tmpFileManager = new TmpFileManager(container: $container); - - $tmpFileManager->isolate(function (TmpFileInterface $tmpFile) { - $this->assertFileExists($tmpFile->getFilename()); - }); - - $this->assertEquals(0, $container->getTmpFilesCount()); - } - - public function testRemoveTmpFile(): void - { - $tmpFileManager = new TmpFileManager(); - $tmpFile = $tmpFileManager->create(); - $tmpFileManager->remove($tmpFile); - - $this->assertFileDoesNotExist($tmpFile->getFilename()); - } - - public function testPurge(): void - { - $tmpFileManager = new TmpFileManager(); - $tmpFile = $tmpFileManager->create(); - $tmpFileManager->purge(); - - $this->assertFileDoesNotExist($tmpFile->getFilename()); - } -} diff --git a/tests/TmpFileTest.php b/tests/TmpFileTest.php deleted file mode 100644 index a8f6370..0000000 --- a/tests/TmpFileTest.php +++ /dev/null @@ -1,25 +0,0 @@ -assertSame('filename', $tmpFile->getFilename()); - } - - public function testStringableBehavior(): void - { - $tmpFile = new TmpFile('filename'); - - $this->assertSame('filename', (string) $tmpFile); - } -} From 617bc80b7fcc0ca3b5c88e11d0401b6af4b2233b Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Mon, 4 Sep 2023 13:15:04 +0300 Subject: [PATCH 02/11] Upgrade package --- .github/workflows/ci.yml | 24 +--- Dockerfile | 11 ++ LICENSE | 2 +- Makefile | 5 + README.md | 6 +- composer.json | 9 +- docs/index.md | 119 ++++++++---------- src/Config/Config.php | 11 +- src/Config/ConfigInterface.php | 2 +- src/Container/Container.php | 11 +- src/Container/ContainerInterface.php | 6 +- src/Event/AbstractTmpFileEvent.php | 8 +- src/Event/AbstractTmpFileManagerEvent.php | 18 +-- src/Event/TmpFileManagerEventArgs.php | 14 +-- src/Filesystem/Filesystem.php | 15 +-- src/Filesystem/FilesystemInterface.php | 2 +- .../GarbageCollectionHandler.php | 11 +- .../GarbageCollectionHandlerInterface.php | 2 +- .../Processor/AsyncProcessor.php | 33 ++--- .../Processor/ProcessorInterface.php | 2 +- .../Processor/SyncProcessor.php | 18 ++- .../UnclosedResourcesHandler.php} | 4 +- .../UnclosedResourcesHandlerInterface.php} | 4 +- src/TmpFileManager.php | 50 +++----- src/TmpFileManagerBuilder.php | 34 ++--- src/TmpFileManagerInterface.php | 2 +- 26 files changed, 193 insertions(+), 230 deletions(-) create mode 100644 Dockerfile create mode 100644 Makefile rename src/Handler/{OpenResourcesHandler/OpenResourcesHandler.php => UnclosedResourcesHandler/UnclosedResourcesHandler.php} (84%) rename src/Handler/{OpenResourcesHandler/OpenResourcesHandlerInterface.php => UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php} (63%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ef4e19..a576ba3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,60 +22,48 @@ jobs: parallel-lint: name: 'ParallelLint' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run ParallelLint run: composer parallel-lint -- --no-progress --ignore-fails psalm: name: 'Psalm' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run Psalm run: composer psalm -- --no-progress --no-cache --output-format=github php-cs-fixer: name: 'PHPCsFixer' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run PHPCsFixer run: composer php-cs-fixer:diff -- --no-interaction --using-cache=no @@ -83,7 +71,6 @@ jobs: name: 'PHPUnit' needs: ['parallel-lint', 'psalm', 'php-cs-fixer'] runs-on: ${{ matrix.operating-system }} - strategy: fail-fast: false matrix: @@ -93,24 +80,21 @@ jobs: php-version: - '8.0' - '8.1' + - '8.2' composer-dependency: - 'lowest' - 'highest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 with: dependency-versions: ${{ matrix.composer-dependency }} - - name: Run PHPUnit run: composer phpunit -- --no-interaction --do-not-cache-result diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..942f681 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.2-cli + +RUN \ + apt-get update ; \ + apt-get install -y unzip ; \ + pecl install pcov ; \ + docker-php-ext-enable pcov ; + +COPY --from=composer:2.4 /usr/bin/composer /usr/local/bin/composer + +WORKDIR /usr/local/packages/tmpfile-manager/ diff --git a/LICENSE b/LICENSE index c99ea19..87a5f6b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Aleksandr Denisyuk +Copyright (c) 2020 Aleksandr Denisyuk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bb7a98f --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +init: + docker build -t tmpfile-manager:8.2 ./ + +exec: + docker run --name tmpfile-manager --rm --interactive --tty --volume ${PWD}:/usr/local/packages/tmpfile-manager/ tmpfile-manager:8.2 /bin/bash diff --git a/README.md b/README.md index 2665a79..f08d9d4 100644 --- a/README.md +++ b/README.md @@ -20,19 +20,21 @@ This package requires PHP 8.0 or later. ## Quick usage -Build TmpFileManager and create a temp file: +Build a temp file manager and create a temp file: ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFileDir(sys_get_temp_dir()) ->withTmpFilePrefix('php') ->build() ; +/** @var TmpFileInterface $tmpFile */ $tmpFile = $tmpFileManager->create(); ``` diff --git a/composer.json b/composer.json index a3de199..7be3bd3 100644 --- a/composer.json +++ b/composer.json @@ -50,15 +50,18 @@ }, "scripts": { "phpunit": "./vendor/bin/phpunit --verbose --colors=always --no-coverage", + "phpunit:clear-cache": "rm ./build/cache/phpunit.cache", "phpunit-coverage": "./vendor/bin/phpunit --verbose --colors=always --coverage-text", "phpunit-coverage-html": "./vendor/bin/phpunit --verbose --colors=always --coverage-html ./build/logs/phpunit-coverage/", "parallel-lint": "./vendor/bin/parallel-lint --colors ./src/ ./tests/", "php-cs-fixer:fix": "./vendor/bin/php-cs-fixer fix --verbose --ansi --show-progress=dots", "php-cs-fixer:diff": "./vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --diff", + "php-cs-fixer:clear-cache": "rm ./build/cache/php-cs-fixer.cache", "psalm": "./vendor/bin/psalm --show-info=true", - "psalm:set-baseline": "@psalm --set-baseline=./psalm-baseline.xml", - "psalm:update-baseline": "@psalm --update-baseline", - "psalm:ignore-baseline": "@psalm --ignore-baseline", + "psalm:clear-cache": "rm -rf ./build/cache/psalm/", + "psalm:set-baseline": "@psalm --set-baseline=./psalm-baseline.xml --no-cache", + "psalm:update-baseline": "@psalm --update-baseline --no-cache", + "psalm:ignore-baseline": "@psalm --ignore-baseline --no-cache", "test": [ "@parallel-lint", "@psalm", diff --git a/docs/index.md b/docs/index.md index 399f806..6eb021b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,23 +12,18 @@ By default, temp files purge automatically, check unclosed resources and garbage collection are off. Below is the default configuration: -```php -withTmpFileDirectory(sys_get_temp_dir()) - ->withTmpFilePrefix('php') - ->build() -; +```text ++--------------------------+--------------------+ +| Option | Default | ++--------------------------+--------------------+ +| temp file dir | sys_get_temp_dir() | +| temp file prefix | php | +| check unclosed resources | [x] | +| garbage collection | [x] | ++--------------------------+--------------------+ ``` -[...] - -## Creating temp files - -[...] +To create a temp file manager, build it with options like below: ```php withTmpFileDirectory(sys_get_temp_dir()) + ->withTmpFileDir(sys_get_temp_dir()) ->withTmpFilePrefix('php') ->build() ; ``` -[...] +All options are not required. + +## Creating temp files + +To create a temporary file, use the `create(): \TmpFile\TmpFileInterface` method. All created temp files are added on the stack and will be removed after PHP is finished: ```php +/** @var \TmpFile\TmpFileInterface $tmpFile */ $tmpFile = $tmpFileManager->create(); ``` - -[...] - +In console commands, use the `isolate(\TmpFile\TmpFileInterface $tmpFile): void` method to create and handle temp files. Temp files will be immediately removed after the finished callback: ```php -$tmpFileManager->isolate(function (TmpFileInterface $tmpFile): void { +$tmpFileManager->isolate(function (\TmpFile\TmpFileInterface $tmpFile): void { // ... }); ``` -[...] +Use one of the following methods to create a temp files for your tasks properly. ## Loading temp files -[...] +To add existing temp files to the temp file manager, use the `load(\TmpFile\TmpFileInterface ...$tmpFiles): void` method: ```php -withTmpFileDirectory(sys_get_temp_dir()) - ->withTmpFilePrefix('php') - ->build() -; - -$files = [ - new \SplFileInfo(__DIR__.'/cat.jpg'), - new \SplFileInfo(__DIR__.'/dog.jpg'), - new \SplFileInfo(__DIR__.'/fish.jpg'), +$tmpFiles = [ + new \TmpFileManager\TmpFile(__DIR__.'/cat.jpg'), + new \TmpFileManager\TmpFile(__DIR__.'/dog.jpg'), + new \TmpFileManager\TmpFile(__DIR__.'/fish.jpg'), ]; -$tmpFileManager->load(...$files); +$tmpFileManager->load(...$tmpFiles); ``` -[...] +The source files will be removed after PHP is finished. ## Removing temp files -[...] - -```php -withTmpFileDirectory(sys_get_temp_dir()) - ->withTmpFilePrefix('php') - ->build() -; - -$tmpFile = $tmpFileManager->create(); -``` - -[...] +By default, created temp files will purge automatically after PHP is finished, but you can remove temp files manually with `remove(\TmpFile\TmpFileInterface $tmpFile): void` method: ```php $tmpFileManager->remove($tmpFile); ``` -[...] +If you need to forcefully purge all temp files, you can use the `purge(): void` method: ```php $tmpFileManager->purge(); ``` -[...] +All temp files will be immediately removed. ## Check unclosed resources -[...] +Before purging temp files temp file manager can check unclosed resources and close opened resources which refer to temp files. Build temp file manager with [UnclosedResourcesHandler](../src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php): ```php withOpenResourcesHandler(new OpenResourcesHandler()) + ->withUnclosedResourcesHandler(new UnclosedResourcesHandler()) ->build() ; +``` + +Now you can ignore opened resources, e. g.: +```php $tmpFile = $tmpFileManager->create(); + $fh = fopen($tmpFile->getFilename(), 'r+'); + fwrite($fh, random_bytes(1024)); // ... @@ -149,14 +127,14 @@ Garbage collection process starts after temp files purging. The probability is c addEventListener(TmpFileOnCreate::class, static function (TmpFileOnCreate $tmpFileOnCreate): void { - // ... - }) + ->withEventListener( + TmpFileOnCreate::class, + static function (TmpFileOnCreate $tmpFileOnCreate): void { + // ... + }, + ) ->build() ; $tmpFile = $tmpFileManager->create(); ``` -[...] +All listeners will be run when the temp file manager fires events. diff --git a/src/Config/Config.php b/src/Config/Config.php index 9396992..3582567 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -6,18 +6,15 @@ final class Config implements ConfigInterface { - private string $tmpFileDirectory; - public function __construct( - ?string $tmpFileDirectory = null, - private string $tmpFilePrefix = 'php', + private string $tmpFileDir, + private string $tmpFilePrefix, ) { - $this->tmpFileDirectory = $tmpFileDirectory ?? sys_get_temp_dir(); } - public function getTmpFileDirectory(): string + public function getTmpFileDir(): string { - return $this->tmpFileDirectory; + return $this->tmpFileDir; } public function getTmpFilePrefix(): string diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 877e623..976a0d0 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -6,7 +6,7 @@ interface ConfigInterface { - public function getTmpFileDirectory(): string; + public function getTmpFileDir(): string; public function getTmpFilePrefix(): string; } diff --git a/src/Container/Container.php b/src/Container/Container.php index 276443b..9ba7287 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -38,7 +38,7 @@ public function removeTmpFile(TmpFileInterface $tmpFile): void $this->tmpFiles->detach($tmpFile); } - public function removeAll(): void + public function clearTmpFiles(): void { $this->tmpFiles->removeAll($this->tmpFiles); } @@ -46,15 +46,20 @@ public function removeAll(): void /** * @return TmpFileInterface[] */ - public function toArray(): array + public function getTmpFiles(): array { - if (0 === $this->count()) { + if ($this->isEmpty()) { return []; } return iterator_to_array($this->tmpFiles, false); } + public function isEmpty(): bool + { + return 0 === $this->count(); + } + public function count(): int { return \count($this->tmpFiles); diff --git a/src/Container/ContainerInterface.php b/src/Container/ContainerInterface.php index 70a058a..f9cdfaa 100644 --- a/src/Container/ContainerInterface.php +++ b/src/Container/ContainerInterface.php @@ -14,10 +14,12 @@ public function hasTmpFile(TmpFileInterface $tmpFile): bool; public function removeTmpFile(TmpFileInterface $tmpFile): void; - public function removeAll(): void; + public function clearTmpFiles(): void; /** * @return TmpFileInterface[] */ - public function toArray(): array; + public function getTmpFiles(): array; + + public function isEmpty(): bool; } diff --git a/src/Event/AbstractTmpFileEvent.php b/src/Event/AbstractTmpFileEvent.php index 86f8242..f257486 100644 --- a/src/Event/AbstractTmpFileEvent.php +++ b/src/Event/AbstractTmpFileEvent.php @@ -8,11 +8,9 @@ abstract class AbstractTmpFileEvent { - private TmpFileInterface $tmpFile; - - public function __construct(TmpFileInterface $tmpFile) - { - $this->tmpFile = $tmpFile; + public function __construct( + private TmpFileInterface $tmpFile, + ) { } public function getTmpFile(): TmpFileInterface diff --git a/src/Event/AbstractTmpFileManagerEvent.php b/src/Event/AbstractTmpFileManagerEvent.php index 2b42db8..bc86c81 100644 --- a/src/Event/AbstractTmpFileManagerEvent.php +++ b/src/Event/AbstractTmpFileManagerEvent.php @@ -10,29 +10,23 @@ abstract class AbstractTmpFileManagerEvent { - private ConfigInterface $config; - private ContainerInterface $container; - private FilesystemInterface $filesystem; - - public function __construct(TmpFileManagerEventArgs $fileManagerEventArgs) - { - $this->config = $fileManagerEventArgs->getConfig(); - $this->container = $fileManagerEventArgs->getContainer(); - $this->filesystem = $fileManagerEventArgs->getFilesystem(); + public function __construct( + private TmpFileManagerEventArgs $fileManagerEventArgs, + ) { } public function getConfig(): ConfigInterface { - return $this->config; + return $this->fileManagerEventArgs->getConfig(); } public function getContainer(): ContainerInterface { - return $this->container; + return $this->fileManagerEventArgs->getContainer(); } public function getFilesystem(): FilesystemInterface { - return $this->filesystem; + return $this->fileManagerEventArgs->getFilesystem(); } } diff --git a/src/Event/TmpFileManagerEventArgs.php b/src/Event/TmpFileManagerEventArgs.php index fd01597..0dfd8ca 100644 --- a/src/Event/TmpFileManagerEventArgs.php +++ b/src/Event/TmpFileManagerEventArgs.php @@ -8,15 +8,11 @@ final class TmpFileManagerEventArgs { - private ConfigInterface $config; - private ContainerInterface $container; - private FilesystemInterface $filesystem; - - public function __construct(ConfigInterface $config, ContainerInterface $container, FilesystemInterface $filesystem) - { - $this->config = $config; - $this->container = $container; - $this->filesystem = $filesystem; + public function __construct( + private ConfigInterface $config, + private ContainerInterface $container, + private FilesystemInterface $filesystem, + ) { } public function getConfig(): ConfigInterface diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index 71d8a52..cfcfd58 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -6,19 +6,20 @@ use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; use TmpFile\TmpFileInterface; +use TmpFileManager\TmpFile; final class Filesystem implements FilesystemInterface { - private SymfonyFilesystem $symfonyFilesystem; - - public function __construct(?SymfonyFilesystem $symfonyFilesystem = null) - { - $this->symfonyFilesystem = $symfonyFilesystem ?? new SymfonyFilesystem(); + public function __construct( + private SymfonyFilesystem $symfonyFilesystem, + ) { } - public function createTmpFile(string $tmpFileDirectory, string $tmpFilePrefix): string + public function createTmpFile(string $tmpFileDir, string $tmpFilePrefix): TmpFileInterface { - return $this->symfonyFilesystem->tempnam($tmpFileDirectory, $tmpFilePrefix); + $filename = $this->symfonyFilesystem->tempnam($tmpFileDir, $tmpFilePrefix); + + return new TmpFile($filename); } public function existsTmpFile(TmpFileInterface $tmpFile): bool diff --git a/src/Filesystem/FilesystemInterface.php b/src/Filesystem/FilesystemInterface.php index 98757b1..e1c170a 100644 --- a/src/Filesystem/FilesystemInterface.php +++ b/src/Filesystem/FilesystemInterface.php @@ -8,7 +8,7 @@ interface FilesystemInterface { - public function createTmpFile(string $tmpFileDirectory, string $tmpFilePrefix): string; + public function createTmpFile(string $tmpFileDir, string $tmpFilePrefix): TmpFileInterface; public function existsTmpFile(TmpFileInterface $tmpFile): bool; diff --git a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php index 588c9e3..b0e13d7 100644 --- a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php +++ b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandler.php @@ -16,15 +16,10 @@ public function __construct( ) { } - public function handle(string $tmpFileDirectory, string $tmpFilePrefix): void + public function handle(string $tmpFileDir, string $tmpFilePrefix): void { - if ($this->isChance($this->probability, $this->divisor)) { - $this->processor->process($tmpFileDirectory, $tmpFilePrefix, $this->lifetime); + if (mt_rand(1, $this->divisor) <= $this->probability) { + $this->processor->process($tmpFileDir, $tmpFilePrefix, $this->lifetime); } } - - private function isChance(int $probability, int $divisor): bool - { - return 0 !== $probability && mt_rand(1, $divisor) > $probability; - } } diff --git a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php index 8f80425..b1c6a75 100644 --- a/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php +++ b/src/Handler/GarbageCollectionHandler/GarbageCollectionHandlerInterface.php @@ -6,5 +6,5 @@ interface GarbageCollectionHandlerInterface { - public function handle(string $tmpFileDirectory, string $tmpFilePrefix): void; + public function handle(string $tmpFileDir, string $tmpFilePrefix): void; } diff --git a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php index 37ad978..d007a48 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php @@ -9,36 +9,37 @@ final class AsyncProcessor implements ProcessorInterface { - private string $command; - /** - * @param string[] $extraCommandDirectories + * @param string[] $extraExecutableCommandDirs */ - public function __construct(array $extraCommandDirectories = []) + public function __construct( + private bool $isParallelProcess = true, + private array $extraExecutableCommandDirs = [], + ) { + } + + public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime): void { - $command = (new ExecutableFinder())->find('find', '/usr/bin/find', $extraCommandDirectories); + $executableCommand = (new ExecutableFinder()) + ->find('find', '/usr/bin/find', $this->extraExecutableCommandDirs) + ; - if (null === $command) { - throw new \RuntimeException('Util "find" isn\'t supporting.'); + if (null === $executableCommand) { + throw new \RuntimeException('Async process can\'t be run because utility "find" not supported.'); } - $this->command = $command; - } - - public function process(string $tmpFileDirectory, string $tmpFilePrefix, int $tmpFileLifetimeInSeconds): void - { $process = new Process([ - $this->command, - $tmpFileDirectory, + $executableCommand, + $tmpFileDir, '-name', $tmpFilePrefix.'*', '-type', 'f', - '-amin', '+'.ceil($tmpFileLifetimeInSeconds / 60), + '-amin', '+'.ceil($lifetime / 60), '-maxdepth', 1, '-delete', ]); $process->setOptions([ - 'create_new_console' => true, + 'create_new_console' => $this->isParallelProcess, ]); $process->start(); diff --git a/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php b/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php index e2e5d98..5c87aa6 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php +++ b/src/Handler/GarbageCollectionHandler/Processor/ProcessorInterface.php @@ -6,5 +6,5 @@ interface ProcessorInterface { - public function process(string $tmpFileDirectory, string $tmpFilePrefix, int $tmpFileLifetimeInSeconds): void; + public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime): void; } diff --git a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php index 7bc8cb9..98e216c 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php @@ -4,30 +4,28 @@ namespace TmpFileManager\Handler\GarbageCollectionHandler\Processor; -use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; use Symfony\Component\Finder\Finder; final class SyncProcessor implements ProcessorInterface { - private Filesystem $filesystem; - - public function __construct(?Filesystem $filesystem = null) - { - $this->filesystem = $filesystem ?? new Filesystem(); + public function __construct( + private SymfonyFilesystem $symfonyFilesystem, + ) { } - public function process(string $tmpFileDirectory, string $tmpFilePrefix, int $tmpFileLifetimeInSeconds): void + public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime): void { $finder = (new Finder()) - ->in($tmpFileDirectory) + ->in($tmpFileDir) ->name($tmpFilePrefix.'*') ->depth('== 0') - ->date('< '.date('Y-m-d H:i:s', time() - $tmpFileLifetimeInSeconds)) + ->date('< '.date('Y-m-d H:i:s', time() - $lifetime)) ->files() ; if ($finder->hasResults()) { - $this->filesystem->remove($finder->getIterator()); + $this->symfonyFilesystem->remove($finder->getIterator()); } } } diff --git a/src/Handler/OpenResourcesHandler/OpenResourcesHandler.php b/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php similarity index 84% rename from src/Handler/OpenResourcesHandler/OpenResourcesHandler.php rename to src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php index b6d715b..f1519ea 100644 --- a/src/Handler/OpenResourcesHandler/OpenResourcesHandler.php +++ b/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace TmpFileManager\Handler\OpenResourcesHandler; +namespace TmpFileManager\Handler\UnclosedResourcesHandler; use TmpFile\TmpFileInterface; -final class OpenResourcesHandler implements OpenResourcesHandlerInterface +final class UnclosedResourcesHandler implements UnclosedResourcesHandlerInterface { /** * @param TmpFileInterface[] $tmpFiles diff --git a/src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php b/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php similarity index 63% rename from src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php rename to src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php index 300629c..21658c8 100644 --- a/src/Handler/OpenResourcesHandler/OpenResourcesHandlerInterface.php +++ b/src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerInterface.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace TmpFileManager\Handler\OpenResourcesHandler; +namespace TmpFileManager\Handler\UnclosedResourcesHandler; use TmpFile\TmpFileInterface; -interface OpenResourcesHandlerInterface +interface UnclosedResourcesHandlerInterface { /** * @param TmpFileInterface[] $tmpFiles diff --git a/src/TmpFileManager.php b/src/TmpFileManager.php index 8c351ea..8669a15 100644 --- a/src/TmpFileManager.php +++ b/src/TmpFileManager.php @@ -5,11 +5,8 @@ namespace TmpFileManager; use Psr\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\EventDispatcher; use TmpFile\TmpFileInterface; -use TmpFileManager\Config\Config; use TmpFileManager\Config\ConfigInterface; -use TmpFileManager\Container\Container; use TmpFileManager\Container\ContainerInterface; use TmpFileManager\Event\TmpFileManagerEventArgs; use TmpFileManager\Event\TmpFileManagerOnFinish; @@ -24,36 +21,29 @@ use TmpFileManager\Event\TmpFileOnLoad; use TmpFileManager\Event\TmpFilePostRemove; use TmpFileManager\Event\TmpFilePreRemove; -use TmpFileManager\Filesystem\Filesystem; use TmpFileManager\Filesystem\FilesystemInterface; final class TmpFileManager implements TmpFileManagerInterface { - private ConfigInterface $config; - private ContainerInterface $container; - private FilesystemInterface $filesystem; - private EventDispatcherInterface $eventDispatcher; - public function __construct( - ?ConfigInterface $config = null, - ?ContainerInterface $container = null, - ?FilesystemInterface $filesystem = null, - ?EventDispatcherInterface $eventDispatcher = null, + private ConfigInterface $config, + private ContainerInterface $container, + private FilesystemInterface $filesystem, + private EventDispatcherInterface $eventDispatcher, ) { - $this->config = $config ?? new Config(); - $this->container = $container ?? new Container(); - $this->filesystem = $filesystem ?? new Filesystem(); - $this->eventDispatcher = $eventDispatcher ?? new EventDispatcher(); $this->eventDispatcher->dispatch(new TmpFileManagerOnStart($this->getTmpFileManagerEventArgs())); register_shutdown_function([$this, 'purge']); } + private function getTmpFileManagerEventArgs(): TmpFileManagerEventArgs + { + return new TmpFileManagerEventArgs($this->config, $this->container, $this->filesystem); + } + public function create(): TmpFileInterface { $this->eventDispatcher->dispatch(new TmpFileManagerPreCreate($this->getTmpFileManagerEventArgs())); - $tmpFileDirectory = $this->config->getTmpFileDirectory(); - $tmpFilePrefix = $this->config->getTmpFilePrefix(); - $tmpFile = new TmpFile($this->filesystem->createTmpFile($tmpFileDirectory, $tmpFilePrefix)); + $tmpFile = $this->filesystem->createTmpFile($this->config->getTmpFileDir(), $this->config->getTmpFilePrefix()); $this->container->addTmpFile($tmpFile); $this->eventDispatcher->dispatch(new TmpFileOnCreate($tmpFile)); $this->eventDispatcher->dispatch(new TmpFileManagerPostCreate($this->getTmpFileManagerEventArgs())); @@ -61,19 +51,17 @@ public function create(): TmpFileInterface return $tmpFile; } - public function load(\SplFileInfo ...$files): void + public function load(TmpFileInterface ...$tmpFiles): void { $this->eventDispatcher->dispatch(new TmpFileManagerPreLoad($this->getTmpFileManagerEventArgs())); - foreach ($files as $file) { - $tmpFile = new TmpFile($file->getPathname()); - $this->eventDispatcher->dispatch(new TmpFileOnLoad($tmpFile)); - + foreach ($tmpFiles as $tmpFile) { if (!$this->filesystem->existsTmpFile($tmpFile)) { throw new \InvalidArgumentException(sprintf('Temp file "%s" doesn\'t exist.', $tmpFile->getFilename())); } $this->container->addTmpFile($tmpFile); + $this->eventDispatcher->dispatch(new TmpFileOnLoad($tmpFile)); } $this->eventDispatcher->dispatch(new TmpFileManagerPostLoad($this->getTmpFileManagerEventArgs())); @@ -109,18 +97,18 @@ public function remove(TmpFileInterface $tmpFile): void public function purge(): void { $this->eventDispatcher->dispatch(new TmpFileManagerPrePurge($this->getTmpFileManagerEventArgs())); - $tmpFiles = $this->container->toArray(); - foreach ($tmpFiles as $tmpFile) { - $this->remove($tmpFile); + if (!$this->container->isEmpty()) { + foreach ($this->container->getTmpFiles() as $tmpFile) { + $this->remove($tmpFile); + } } $this->eventDispatcher->dispatch(new TmpFileManagerPostPurge($this->getTmpFileManagerEventArgs())); - $this->eventDispatcher->dispatch(new TmpFileManagerOnFinish($this->getTmpFileManagerEventArgs())); } - private function getTmpFileManagerEventArgs(): TmpFileManagerEventArgs + public function __destruct() { - return new TmpFileManagerEventArgs($this->config, $this->container, $this->filesystem); + $this->eventDispatcher->dispatch(new TmpFileManagerOnFinish($this->getTmpFileManagerEventArgs())); } } diff --git a/src/TmpFileManagerBuilder.php b/src/TmpFileManagerBuilder.php index b010916..29414b3 100644 --- a/src/TmpFileManagerBuilder.php +++ b/src/TmpFileManagerBuilder.php @@ -13,27 +13,29 @@ use TmpFileManager\Event\TmpFileManagerPrePurge; use TmpFileManager\Filesystem\Filesystem; use TmpFileManager\Handler\GarbageCollectionHandler\GarbageCollectionHandlerInterface; -use TmpFileManager\Handler\OpenResourcesHandler\OpenResourcesHandlerInterface; +use TmpFileManager\Handler\UnclosedResourcesHandler\UnclosedResourcesHandlerInterface; final class TmpFileManagerBuilder implements TmpFileManagerBuilderInterface { + private string $tmpFileDir; + private string $tmpFilePrefix; private SymfonyFilesystem $symfonyFilesystem; private EventDispatcherInterface $eventDispatcher; - private ?string $tmpFileDirectory = null; - private string $tmpFilePrefix = 'php'; public function __construct( - ?SymfonyFilesystem $symfonyFilesystem = null, - ?EventDispatcherInterface $eventDispatcher = null, + SymfonyFilesystem $symfonyFilesystem = null, + EventDispatcherInterface $eventDispatcher = null, ) { + $this->tmpFileDir = sys_get_temp_dir(); + $this->tmpFilePrefix = 'php'; $this->symfonyFilesystem = $symfonyFilesystem ?? new SymfonyFilesystem(); $this->eventDispatcher = $eventDispatcher ?? new EventDispatcher(); } - public function withTmpFileDirectory(string $tmpFileDirectory): self + public function withTmpFileDir(string $tmpFileDir): self { $self = clone $this; - $self->tmpFileDirectory = $tmpFileDirectory; + $self->tmpFileDir = $tmpFileDir; return $self; } @@ -46,14 +48,14 @@ public function withTmpFilePrefix(string $tmpFilePrefix): self return $self; } - public function withOpenResourcesHandler(OpenResourcesHandlerInterface $openResourcesHandler): self + public function withUnclosedResourcesHandler(UnclosedResourcesHandlerInterface $unclosedResourcesHandler): self { $self = clone $this; - $self->addEventListener( + $self->withEventListener( TmpFileManagerPrePurge::class, - static function (TmpFileManagerPrePurge $tmpFileManagerPrePurge) use ($openResourcesHandler): void { - $openResourcesHandler->handle( - tmpFiles: $tmpFileManagerPrePurge->getContainer()->toArray(), + static function (TmpFileManagerPrePurge $tmpFileManagerPrePurge) use ($unclosedResourcesHandler): void { + $unclosedResourcesHandler->handle( + tmpFiles: $tmpFileManagerPrePurge->getContainer()->getTmpFiles(), ); }, ); @@ -64,11 +66,11 @@ static function (TmpFileManagerPrePurge $tmpFileManagerPrePurge) use ($openResou public function withGarbageCollectionHandler(GarbageCollectionHandlerInterface $garbageCollectionHandler): self { $self = clone $this; - $self->addEventListener( + $self->withEventListener( TmpFileManagerPostPurge::class, static function (TmpFileManagerPostPurge $tmpFileManagerPostPurge) use ($garbageCollectionHandler): void { $garbageCollectionHandler->handle( - tmpFileDirectory: $tmpFileManagerPostPurge->getConfig()->getTmpFileDirectory(), + tmpFileDir: $tmpFileManagerPostPurge->getConfig()->getTmpFileDir(), tmpFilePrefix: $tmpFileManagerPostPurge->getConfig()->getTmpFilePrefix(), ); }, @@ -77,7 +79,7 @@ static function (TmpFileManagerPostPurge $tmpFileManagerPostPurge) use ($garbage return $self; } - public function addEventListener(string $eventName, callable $listenerCallback): self + public function withEventListener(string $eventName, callable $listenerCallback): self { $self = clone $this; $self->eventDispatcher->addListener($eventName, $listenerCallback); @@ -88,7 +90,7 @@ public function addEventListener(string $eventName, callable $listenerCallback): public function build(): TmpFileManagerInterface { return new TmpFileManager( - config: new Config($this->tmpFileDirectory, $this->tmpFilePrefix), + config: new Config($this->tmpFileDir, $this->tmpFilePrefix), container: new Container(), filesystem: new Filesystem($this->symfonyFilesystem), eventDispatcher: $this->eventDispatcher, diff --git a/src/TmpFileManagerInterface.php b/src/TmpFileManagerInterface.php index 0e54463..2c6b203 100644 --- a/src/TmpFileManagerInterface.php +++ b/src/TmpFileManagerInterface.php @@ -10,7 +10,7 @@ interface TmpFileManagerInterface { public function create(): TmpFileInterface; - public function load(\SplFileInfo ...$files): void; + public function load(TmpFileInterface ...$tmpFiles): void; public function isolate(callable $callback): void; From 2ddd0537ef2758e51696d051a06618f5c612cbe3 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Sun, 10 Sep 2023 19:20:53 +0300 Subject: [PATCH 03/11] Fix psalm in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a576ba3..4f763cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: Install dependencies uses: ramsey/composer-install@v2 - name: Run Psalm - run: composer psalm -- --no-progress --no-cache --output-format=github + run: composer psalm -- --show-info=false --no-progress --no-suggestions --no-cache php-cs-fixer: name: 'PHPCsFixer' From 9acef414d23cd1f540ade07cec31c44576cbb5b9 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Sun, 10 Sep 2023 19:39:18 +0300 Subject: [PATCH 04/11] Update docs --- docs/index.md | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6eb021b..1517e1a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,16 +41,16 @@ All options are not required. ## Creating temp files -To create a temporary file, use the `create(): \TmpFile\TmpFileInterface` method. All created temp files are added on the stack and will be removed after PHP is finished: +To create a temporary file, use the `TmpFileManager\TmpFileManagerInterface::create(): TmpFile\TmpFileInterface` method. All created temp files are added on the stack and will be removed after PHP is finished: ```php -/** @var \TmpFile\TmpFileInterface $tmpFile */ +/** @var TmpFile\TmpFileInterface $tmpFile */ $tmpFile = $tmpFileManager->create(); ``` -In console commands, use the `isolate(\TmpFile\TmpFileInterface $tmpFile): void` method to create and handle temp files. Temp files will be immediately removed after the finished callback: +In console commands, use the `TmpFileManager\TmpFileManagerInterface::isolate(TmpFile\TmpFileInterface $tmpFile): void` method to create and handle temp files. Temp files will be immediately removed after the finished callback: ```php -$tmpFileManager->isolate(function (\TmpFile\TmpFileInterface $tmpFile): void { +$tmpFileManager->isolate(function (TmpFile\TmpFileInterface $tmpFile): void { // ... }); ``` @@ -59,13 +59,13 @@ Use one of the following methods to create a temp files for your tasks properly. ## Loading temp files -To add existing temp files to the temp file manager, use the `load(\TmpFile\TmpFileInterface ...$tmpFiles): void` method: +To add existing temp files to the temp file manager, use the `TmpFileManager\TmpFileManagerInterface::load(TmpFile\TmpFileInterface ...$tmpFiles): void` method: ```php $tmpFiles = [ - new \TmpFileManager\TmpFile(__DIR__.'/cat.jpg'), - new \TmpFileManager\TmpFile(__DIR__.'/dog.jpg'), - new \TmpFileManager\TmpFile(__DIR__.'/fish.jpg'), + new TmpFileManager\TmpFile(__DIR__.'/cat.jpg'), + new TmpFileManager\TmpFile(__DIR__.'/dog.jpg'), + new TmpFileManager\TmpFile(__DIR__.'/fish.jpg'), ]; $tmpFileManager->load(...$tmpFiles); @@ -75,13 +75,13 @@ The source files will be removed after PHP is finished. ## Removing temp files -By default, created temp files will purge automatically after PHP is finished, but you can remove temp files manually with `remove(\TmpFile\TmpFileInterface $tmpFile): void` method: +By default, created temp files will purge automatically after PHP is finished, but you can remove temp files manually with `TmpFileManager\TmpFileManagerInterface::remove(TmpFile\TmpFileInterface $tmpFile): void` method: ```php $tmpFileManager->remove($tmpFile); ``` -If you need to forcefully purge all temp files, you can use the `purge(): void` method: +If you need to forcefully purge all temp files, you can use the `TmpFileManager\TmpFileManagerInterface::purge(): void` method: ```php $tmpFileManager->purge(); @@ -91,7 +91,7 @@ All temp files will be immediately removed. ## Check unclosed resources -Before purging temp files temp file manager can check unclosed resources and close opened resources which refer to temp files. Build temp file manager with [UnclosedResourcesHandler](../src/Handler/UnclosedResourcesHandler/UnclosedResourcesHandler.php): +Before purging temp files temp file manager can check unclosed resources and close opened resources which refer to temp files. Build temp file manager with unclosed resources' handler like below: ```php Date: Thu, 16 May 2024 20:32:23 +0300 Subject: [PATCH 05/11] Update temp file manager --- src/Event/AbstractTmpFileEvent.php | 3 +++ src/Event/AbstractTmpFileManagerEvent.php | 11 +++++--- src/Event/TmpFileManagerEventArgs.php | 3 +++ src/Filesystem/Filesystem.php | 16 ++++++----- .../Processor/AsyncProcessor.php | 27 ++++++++++--------- .../Processor/SyncProcessor.php | 12 +++++---- src/TmpFile.php | 3 +++ src/TmpFileManager.php | 6 ++++- src/TmpFileManagerBuilder.php | 25 +++++++++++------ tests/.gitkeep | 0 10 files changed, 68 insertions(+), 38 deletions(-) delete mode 100644 tests/.gitkeep diff --git a/src/Event/AbstractTmpFileEvent.php b/src/Event/AbstractTmpFileEvent.php index f257486..7c74502 100644 --- a/src/Event/AbstractTmpFileEvent.php +++ b/src/Event/AbstractTmpFileEvent.php @@ -6,6 +6,9 @@ use TmpFile\TmpFileInterface; +/** + * @codeCoverageIgnore + */ abstract class AbstractTmpFileEvent { public function __construct( diff --git a/src/Event/AbstractTmpFileManagerEvent.php b/src/Event/AbstractTmpFileManagerEvent.php index bc86c81..0d6b96f 100644 --- a/src/Event/AbstractTmpFileManagerEvent.php +++ b/src/Event/AbstractTmpFileManagerEvent.php @@ -8,25 +8,28 @@ use TmpFileManager\Container\ContainerInterface; use TmpFileManager\Filesystem\FilesystemInterface; +/** + * @codeCoverageIgnore + */ abstract class AbstractTmpFileManagerEvent { public function __construct( - private TmpFileManagerEventArgs $fileManagerEventArgs, + private TmpFileManagerEventArgs $args, ) { } public function getConfig(): ConfigInterface { - return $this->fileManagerEventArgs->getConfig(); + return $this->args->getConfig(); } public function getContainer(): ContainerInterface { - return $this->fileManagerEventArgs->getContainer(); + return $this->args->getContainer(); } public function getFilesystem(): FilesystemInterface { - return $this->fileManagerEventArgs->getFilesystem(); + return $this->args->getFilesystem(); } } diff --git a/src/Event/TmpFileManagerEventArgs.php b/src/Event/TmpFileManagerEventArgs.php index 0dfd8ca..f8c6754 100644 --- a/src/Event/TmpFileManagerEventArgs.php +++ b/src/Event/TmpFileManagerEventArgs.php @@ -6,6 +6,9 @@ use TmpFileManager\Container\ContainerInterface; use TmpFileManager\Filesystem\FilesystemInterface; +/** + * @codeCoverageIgnore + */ final class TmpFileManagerEventArgs { public function __construct( diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index cfcfd58..e6e191f 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -4,31 +4,33 @@ namespace TmpFileManager\Filesystem; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Symfony\Component\Filesystem\Filesystem as Fs; use TmpFile\TmpFileInterface; use TmpFileManager\TmpFile; final class Filesystem implements FilesystemInterface { - public function __construct( - private SymfonyFilesystem $symfonyFilesystem, - ) { + private Fs $fs; + + public function __construct(Fs $fs = null) + { + $this->fs = $fs ?? new Fs(); } public function createTmpFile(string $tmpFileDir, string $tmpFilePrefix): TmpFileInterface { - $filename = $this->symfonyFilesystem->tempnam($tmpFileDir, $tmpFilePrefix); + $filename = $this->fs->tempnam($tmpFileDir, $tmpFilePrefix); return new TmpFile($filename); } public function existsTmpFile(TmpFileInterface $tmpFile): bool { - return $this->symfonyFilesystem->exists($tmpFile->getFilename()); + return $this->fs->exists($tmpFile->getFilename()); } public function removeTmpFile(TmpFileInterface $tmpFile): void { - $this->symfonyFilesystem->remove($tmpFile->getFilename()); + $this->fs->remove($tmpFile->getFilename()); } } diff --git a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php index d007a48..39be4e8 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php @@ -7,29 +7,30 @@ use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; +/** + * @requires OS Linux + */ final class AsyncProcessor implements ProcessorInterface { /** - * @param string[] $extraExecutableCommandDirs + * @param string[] $extraDirs */ public function __construct( - private bool $isParallelProcess = true, - private array $extraExecutableCommandDirs = [], + private bool $isParallel = true, + private array $extraDirs = [], ) { } public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime): void { - $executableCommand = (new ExecutableFinder()) - ->find('find', '/usr/bin/find', $this->extraExecutableCommandDirs) - ; + $executable = (new ExecutableFinder())->find('find', '/usr/bin/find', $this->extraDirs); - if (null === $executableCommand) { - throw new \RuntimeException('Async process can\'t be run because utility "find" not supported.'); + if (null === $executable) { + throw new \RuntimeException('Async process can\'t be run because utility "find" not supported.'); // @codeCoverageIgnore } $process = new Process([ - $executableCommand, + $executable, $tmpFileDir, '-name', $tmpFilePrefix.'*', '-type', 'f', @@ -38,10 +39,10 @@ public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime '-delete', ]); - $process->setOptions([ - 'create_new_console' => $this->isParallelProcess, - ]); - $process->start(); + + if (!$this->isParallel) { + $process->wait(); + } } } diff --git a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php index 98e216c..76fc3f2 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php @@ -4,14 +4,16 @@ namespace TmpFileManager\Handler\GarbageCollectionHandler\Processor; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Symfony\Component\Filesystem\Filesystem as Fs; use Symfony\Component\Finder\Finder; final class SyncProcessor implements ProcessorInterface { - public function __construct( - private SymfonyFilesystem $symfonyFilesystem, - ) { + private Fs $fs; + + public function __construct(Fs $fs = null) + { + $this->fs = $fs ?? new Fs(); } public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime): void @@ -25,7 +27,7 @@ public function process(string $tmpFileDir, string $tmpFilePrefix, int $lifetime ; if ($finder->hasResults()) { - $this->symfonyFilesystem->remove($finder->getIterator()); + $this->fs->remove($finder->getIterator()); } } } diff --git a/src/TmpFile.php b/src/TmpFile.php index 38f741e..cffff4c 100644 --- a/src/TmpFile.php +++ b/src/TmpFile.php @@ -6,6 +6,9 @@ use TmpFile\TmpFileInterface; +/** + * @codeCoverageIgnore + */ final class TmpFile implements TmpFileInterface { public function __construct( diff --git a/src/TmpFileManager.php b/src/TmpFileManager.php index 8669a15..6ea77df 100644 --- a/src/TmpFileManager.php +++ b/src/TmpFileManager.php @@ -30,9 +30,13 @@ public function __construct( private ContainerInterface $container, private FilesystemInterface $filesystem, private EventDispatcherInterface $eventDispatcher, + private bool $autoPurge = true, ) { $this->eventDispatcher->dispatch(new TmpFileManagerOnStart($this->getTmpFileManagerEventArgs())); - register_shutdown_function([$this, 'purge']); + + if ($this->autoPurge) { + register_shutdown_function([$this, 'purge']); + } } private function getTmpFileManagerEventArgs(): TmpFileManagerEventArgs diff --git a/src/TmpFileManagerBuilder.php b/src/TmpFileManagerBuilder.php index 29414b3..5a0b938 100644 --- a/src/TmpFileManagerBuilder.php +++ b/src/TmpFileManagerBuilder.php @@ -6,7 +6,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Symfony\Component\Filesystem\Filesystem as Fs; use TmpFileManager\Config\Config; use TmpFileManager\Container\Container; use TmpFileManager\Event\TmpFileManagerPostPurge; @@ -19,16 +19,16 @@ final class TmpFileManagerBuilder implements TmpFileManagerBuilderInterface { private string $tmpFileDir; private string $tmpFilePrefix; - private SymfonyFilesystem $symfonyFilesystem; + private bool $autoPurge; + private Fs $fs; private EventDispatcherInterface $eventDispatcher; - public function __construct( - SymfonyFilesystem $symfonyFilesystem = null, - EventDispatcherInterface $eventDispatcher = null, - ) { + public function __construct(Fs $fs = null, EventDispatcherInterface $eventDispatcher = null) + { $this->tmpFileDir = sys_get_temp_dir(); $this->tmpFilePrefix = 'php'; - $this->symfonyFilesystem = $symfonyFilesystem ?? new SymfonyFilesystem(); + $this->autoPurge = true; + $this->fs = $fs ?? new Fs(); $this->eventDispatcher = $eventDispatcher ?? new EventDispatcher(); } @@ -48,6 +48,14 @@ public function withTmpFilePrefix(string $tmpFilePrefix): self return $self; } + public function withoutAutoPurge(): self + { + $self = clone $this; + $self->autoPurge = false; + + return $self; + } + public function withUnclosedResourcesHandler(UnclosedResourcesHandlerInterface $unclosedResourcesHandler): self { $self = clone $this; @@ -92,8 +100,9 @@ public function build(): TmpFileManagerInterface return new TmpFileManager( config: new Config($this->tmpFileDir, $this->tmpFilePrefix), container: new Container(), - filesystem: new Filesystem($this->symfonyFilesystem), + filesystem: new Filesystem($this->fs), eventDispatcher: $this->eventDispatcher, + autoPurge: $this->autoPurge, ); } } diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 From 580bca1716b470e9865f36971d41907709611774 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 16 May 2024 20:32:36 +0300 Subject: [PATCH 06/11] Update docs --- README.md | 1 + docs/index.md | 32 ++++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f08d9d4..840e498 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ All temp files created the manager will be purged automatically by default. - [Creating temp files](docs/index.md#creating-temp-files) - [Loading temp files](docs/index.md#loading-temp-files) - [Removing temp files](docs/index.md#removing-temp-files) +- [Auto-purging](docs/index.md#auto-purging) - [Check unclosed resources](docs/index.md#check-unclosed-resources) - [Garbage collection](docs/index.md#garbage-collection) - [Lifecycle events](docs/index.md#lifecycle-events) diff --git a/docs/index.md b/docs/index.md index 1517e1a..4322454 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,6 +4,7 @@ - [Creating temp files](#creating-temp-files) - [Loading temp files](#loading-temp-files) - [Removing temp files](#removing-temp-files) +- [Auto-purging](#auto-purging) - [Check unclosed resources](#check-unclosed-resources) - [Garbage collection](#garbage-collection) - [Lifecycle events](#lifecycle-events) @@ -18,6 +19,7 @@ By default, temp files purge automatically, check unclosed resources and garbage +--------------------------+--------------------+ | temp file dir | sys_get_temp_dir() | | temp file prefix | php | +| auto purge | [v] | | check unclosed resources | [x] | | garbage collection | [x] | +--------------------------+--------------------+ @@ -50,7 +52,7 @@ $tmpFile = $tmpFileManager->create(); In console commands, use the `TmpFileManager\TmpFileManagerInterface::isolate(TmpFile\TmpFileInterface $tmpFile): void` method to create and handle temp files. Temp files will be immediately removed after the finished callback: ```php -$tmpFileManager->isolate(function (TmpFile\TmpFileInterface $tmpFile): void { +$tmpFileManager->isolate(static function (TmpFile\TmpFileInterface $tmpFile): void { // ... }); ``` @@ -89,6 +91,23 @@ $tmpFileManager->purge(); All temp files will be immediately removed. +## Auto-purging + +By default, temp files purge automatically. The feature based on `register_shutdown_function` function and init every time when temp file manager is started. For difficult use cases you can turn off auto purging: + +```php +withoutAutoPurge('php') + ->build() +; +``` + +Think of auto purging as fallback feature to shoot all problems with removing. + ## Check unclosed resources Before purging temp files temp file manager can check unclosed resources and close opened resources which refer to temp files. Build temp file manager with unclosed resources' handler like below: @@ -133,7 +152,7 @@ use TmpFileManager\TmpFileManagerBuilder; $garbageCollectionHandler = new GarbageCollectionHandler( probability: 1, divisor: 100, - lifetime: 3600, + lifetime: 3_600, processor: new AsyncProcessor(), ); @@ -207,14 +226,11 @@ To register an event listener, use the `TmpFileManager\TmpFileManagerBuilder::wi use TmpFileManager\TmpFileManagerBuilder; use TmpFileManager\Event\TmpFileOnCreate; +use TmpFileManager\Event\TmpFilePostRemove; $tmpFileManager = (new TmpFileManagerBuilder()) - ->withEventListener( - TmpFileOnCreate::class, - static function (TmpFileOnCreate $tmpFileOnCreate): void { - // ... - }, - ) + ->withEventListener(TmpFileOnCreate::class, static fn (TmpFileOnCreate $tmpFileOnCreate) => /* ... */) + ->withEventListener(TmpFilePostRemove::class, static fn (TmpFileOnCreate $tmpFileOnCreate) => /* ... */) ->build() ; From 309b005ace2c805ba8f85fad366107faf36a3dbc Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 16 May 2024 20:32:47 +0300 Subject: [PATCH 07/11] Add tests --- tests/Config/ConfigTest.php | 22 ++ tests/Container/ContainerTest.php | 90 +++++++++ tests/Event/TmpFileEventSpy.php | 52 +++++ tests/Event/TmpFileManagerEventSpy.php | 60 ++++++ tests/Filesystem/FilesystemTest.php | 44 ++++ .../GarbageCollectionHandlerTest.php | 30 +++ .../Processor/AsyncProcessorTest.php | 26 +++ .../Processor/SyncProcessorTest.php | 24 +++ .../UnclosedResourcesHandlerTest.php | 28 +++ tests/TmpFileMangerBuilderTest.php | 100 +++++++++ tests/TmpFileMangerLifecycleTest.php | 190 ++++++++++++++++++ tests/TmpFileMangerTest.php | 147 ++++++++++++++ 12 files changed, 813 insertions(+) create mode 100644 tests/Config/ConfigTest.php create mode 100644 tests/Container/ContainerTest.php create mode 100644 tests/Event/TmpFileEventSpy.php create mode 100644 tests/Event/TmpFileManagerEventSpy.php create mode 100644 tests/Filesystem/FilesystemTest.php create mode 100644 tests/Handler/GarbageCollectionHandler/GarbageCollectionHandlerTest.php create mode 100644 tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php create mode 100644 tests/Handler/GarbageCollectionHandler/Processor/SyncProcessorTest.php create mode 100644 tests/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerTest.php create mode 100644 tests/TmpFileMangerBuilderTest.php create mode 100644 tests/TmpFileMangerLifecycleTest.php create mode 100644 tests/TmpFileMangerTest.php diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php new file mode 100644 index 0000000..0658c28 --- /dev/null +++ b/tests/Config/ConfigTest.php @@ -0,0 +1,22 @@ +assertSame(sys_get_temp_dir(), $config->getTmpFileDir()); + $this->assertSame('php', $config->getTmpFilePrefix()); + } +} diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php new file mode 100644 index 0000000..a869052 --- /dev/null +++ b/tests/Container/ContainerTest.php @@ -0,0 +1,90 @@ +addTmpFile($tmpFile); + + $this->assertCount(1, $container->getTmpFiles()); + } + + public function testAddTheSameTmpFile(): void + { + $container = new Container(); + $tmpFile = new TmpFile('meow.txt'); + $container->addTmpFile($tmpFile); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Temp file "meow.txt" has been already added.'); + + $container->addTmpFile($tmpFile); + } + + public function testHasTmpFile(): void + { + $container = new Container(); + $tmpFile = new TmpFile('meow.txt'); + + $container->addTmpFile($tmpFile); + + $this->assertTrue($container->hasTmpFile($tmpFile)); + } + + public function testRemoveTmpFile(): void + { + $container = new Container(); + $tmpFile = new TmpFile('meow.txt'); + $container->addTmpFile($tmpFile); + + $container->removeTmpFile($tmpFile); + + $this->assertCount(0, $container->getTmpFiles()); + } + + public function testRemoveNotAddedTmpFile(): void + { + $container = new Container(); + $tmpFile = new TmpFile('meow.txt'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Temp file "meow.txt" hasn\'t been added yet.'); + + $container->removeTmpFile($tmpFile); + } + + public function testCleatTmpFiles(): void + { + $container = new Container(); + $container->addTmpFile(new TmpFile('cat.jpg')); + $container->addTmpFile(new TmpFile('dog.jpg')); + $container->addTmpFile(new TmpFile('fish.jpg')); + + $container->clearTmpFiles(); + + $this->assertCount(0, $container->getTmpFiles()); + } + + public function testGetTmpFiles(): void + { + $container = new Container(); + $container->addTmpFile(new TmpFile('cat.jpg')); + $container->addTmpFile(new TmpFile('dog.jpg')); + $container->addTmpFile(new TmpFile('fish.jpg')); + + $tmpFiles = $container->getTmpFiles(); + + $this->assertNotEmpty($tmpFiles); + } +} diff --git a/tests/Event/TmpFileEventSpy.php b/tests/Event/TmpFileEventSpy.php new file mode 100644 index 0000000..7607dde --- /dev/null +++ b/tests/Event/TmpFileEventSpy.php @@ -0,0 +1,52 @@ + $eventsCounter + * @param array $tmpFiles + */ + public function __construct( + private array $eventsCounter = [], + private array $tmpFiles = [], + ) { + } + + public function __invoke(AbstractTmpFileEvent $event): void + { + if (!isset($this->eventsCounter[$event::class])) { + $this->eventsCounter[$event::class] = 0; + } + + ++$this->eventsCounter[$event::class]; + $this->tmpFiles[$event->getTmpFile()->getFilename()] = $event->getTmpFile(); + } + + public function getEventsCount(string $className = null): int + { + if (null === $className) { + return array_sum($this->eventsCounter); + } + + if (!isset($this->eventsCounter[$className])) { + return 0; + } + + return $this->eventsCounter[$className]; + } + + /** + * @return TmpFileInterface[] + */ + public function getTmpFiles(): array + { + return array_values($this->tmpFiles); + } +} diff --git a/tests/Event/TmpFileManagerEventSpy.php b/tests/Event/TmpFileManagerEventSpy.php new file mode 100644 index 0000000..bd4404a --- /dev/null +++ b/tests/Event/TmpFileManagerEventSpy.php @@ -0,0 +1,60 @@ + $eventsCounter + * @param array $tmpFilesCount + */ + public function __construct( + private array $eventsCounter = [], + private array $tmpFilesCount = [], + ) { + } + + public function __invoke(AbstractTmpFileManagerEvent $event): void + { + if (!isset($this->eventsCounter[$event::class])) { + $this->eventsCounter[$event::class] = 0; + } + + if (!isset($this->tmpFilesCount[$event::class])) { + $this->tmpFilesCount[$event::class] = 0; + } + + ++$this->eventsCounter[$event::class]; + $this->tmpFilesCount[$event::class] = $event->getContainer()->count(); + } + + public function getEventsCount(string $className = null): int + { + if (null === $className) { + return array_sum($this->eventsCounter); + } + + if (!isset($this->eventsCounter[$className])) { + return 0; + } + + return $this->eventsCounter[$className]; + } + + public function getTmpFilesCount(string $className = null): int + { + if (null === $className) { + return array_sum($this->tmpFilesCount); + } + + if (!isset($this->tmpFilesCount[$className])) { + return 0; + } + + return $this->tmpFilesCount[$className]; + } +} diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php new file mode 100644 index 0000000..368ad23 --- /dev/null +++ b/tests/Filesystem/FilesystemTest.php @@ -0,0 +1,44 @@ +createTmpFile(sys_get_temp_dir(), 'php'); + + $this->assertFileExists($tmpFile->getFilename()); + + $filesystem->removeTmpFile($tmpFile); + } + + public function testExistTmpFile(): void + { + $filesystem = new Filesystem(); + + $tmpFile = $filesystem->createTmpFile(sys_get_temp_dir(), 'php'); + + $this->assertTrue($filesystem->existsTmpFile($tmpFile)); + + $filesystem->removeTmpFile($tmpFile); + } + + public function testRemoveTmpFile(): void + { + $filesystem = new Filesystem(); + + $tmpFile = $filesystem->createTmpFile(sys_get_temp_dir(), 'php'); + + $filesystem->removeTmpFile($tmpFile); + + $this->assertFileDoesNotExist($tmpFile->getFilename()); + } +} diff --git a/tests/Handler/GarbageCollectionHandler/GarbageCollectionHandlerTest.php b/tests/Handler/GarbageCollectionHandler/GarbageCollectionHandlerTest.php new file mode 100644 index 0000000..fc192f6 --- /dev/null +++ b/tests/Handler/GarbageCollectionHandler/GarbageCollectionHandlerTest.php @@ -0,0 +1,30 @@ +tempnam(sys_get_temp_dir(), 'php'); + $fs->touch($tmpFile, time() - 7_200); + + $handler = new GarbageCollectionHandler( + probability: 100, + divisor: 1, + lifetime: 3_600, + processor: new SyncProcessor(), + ); + $handler->handle(sys_get_temp_dir(), 'php'); + + $this->assertFileDoesNotExist($tmpFile); + } +} diff --git a/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php b/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php new file mode 100644 index 0000000..764f7b9 --- /dev/null +++ b/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php @@ -0,0 +1,26 @@ +tempnam(sys_get_temp_dir(), 'php'); + $fs->touch($tmpFile, time() - 7_200); + + $processor = new AsyncProcessor( + isParallel: false, + ); + $processor->process(sys_get_temp_dir(), 'php', 3_600); + + $this->assertFileDoesNotExist($tmpFile); + } +} diff --git a/tests/Handler/GarbageCollectionHandler/Processor/SyncProcessorTest.php b/tests/Handler/GarbageCollectionHandler/Processor/SyncProcessorTest.php new file mode 100644 index 0000000..cda90ab --- /dev/null +++ b/tests/Handler/GarbageCollectionHandler/Processor/SyncProcessorTest.php @@ -0,0 +1,24 @@ +tempnam(sys_get_temp_dir(), 'php'); + $fs->touch($tmpFile, time() - 7_200); + + $processor = new SyncProcessor(); + $processor->process(sys_get_temp_dir(), 'php', 3_600); + + $this->assertFileDoesNotExist($tmpFile); + } +} diff --git a/tests/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerTest.php b/tests/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerTest.php new file mode 100644 index 0000000..8a8231a --- /dev/null +++ b/tests/Handler/UnclosedResourcesHandler/UnclosedResourcesHandlerTest.php @@ -0,0 +1,28 @@ +createTmpFile(sys_get_temp_dir(), 'php'); + $fh = fopen($tmpFile->getFilename(), 'r'); + + try { + $handler = new UnclosedResourcesHandler(); + $handler->handle([$tmpFile]); + + $this->assertFalse(\is_resource($fh)); + } finally { + $filesystem->removeTmpFile($tmpFile); + } + } +} diff --git a/tests/TmpFileMangerBuilderTest.php b/tests/TmpFileMangerBuilderTest.php new file mode 100644 index 0000000..34b9055 --- /dev/null +++ b/tests/TmpFileMangerBuilderTest.php @@ -0,0 +1,100 @@ +withTmpFileDir(sys_get_temp_dir()) + ->build() + ; + + $tmpFile = $tmpFileManager->create(); + + $this->assertStringStartsWith(sys_get_temp_dir(), $tmpFile->getFilename()); + } + + public function testBuildWithTmpFilePrefix(): void + { + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withTmpFileDir(sys_get_temp_dir()) + ->withTmpFilePrefix('php') + ->build() + ; + + $tmpFile = $tmpFileManager->create(); + + $this->assertStringStartsWith(sys_get_temp_dir().'/php', $tmpFile->getFilename()); + } + + public function testBuildTmpFileManagerWithoutAutoPurge(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withoutAutoPurge() + ->withEventListener(TmpFileManagerPostPurge::class, $spy) + ->build() + ; + + $tmpFileManager->create(); + $tmpFileManager->purge(); + + $this->assertEquals(0, $spy->getTmpFilesCount()); + } + + public function testBuildWithUnclosedResourcesHandler(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withUnclosedResourcesHandler(new UnclosedResourcesHandler()) + ->withEventListener(TmpFileManagerPrePurge::class, $spy) + ->build() + ; + + $tmpFile = $tmpFileManager->create(); + $fh = fopen($tmpFile->getFilename(), 'r'); + $tmpFileManager->purge(); + + $this->assertFalse(\is_resource($fh)); + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testBuildWithGarbageCollectionHandler(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withoutAutoPurge() + ->withGarbageCollectionHandler(new GarbageCollectionHandler( + probability: 100, + divisor: 1, + lifetime: 3_600, + processor: new SyncProcessor(), + )) + ->withEventListener(TmpFileManagerPostPurge::class, $spy) + ->build() + ; + + $fs = new Fs(); + $tmpFile = new TmpFile($fs->tempnam(sys_get_temp_dir(), 'php')); + $fs->touch($tmpFile->getFilename(), time() - 7_200); + $tmpFileManager->purge(); + + $this->assertFileDoesNotExist($tmpFile->getFilename()); + $this->assertEquals(1, $spy->getEventsCount()); + } +} diff --git a/tests/TmpFileMangerLifecycleTest.php b/tests/TmpFileMangerLifecycleTest.php new file mode 100644 index 0000000..7471013 --- /dev/null +++ b/tests/TmpFileMangerLifecycleTest.php @@ -0,0 +1,190 @@ +withEventListener(TmpFileManagerOnStart::class, $spy) + ->build() + ; + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerPreCreate(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPreCreate::class, $spy) + ->build() + ; + + $tmpFileManager->create(); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileOnCreate(): void + { + $spy = new TmpFileEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileOnCreate::class, $spy) + ->build() + ; + + $tmpFileManager->create(); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerPostCreate(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPostCreate::class, $spy) + ->build() + ; + + $tmpFileManager->create(); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerPreLoad(): void + { + $spy = new TmpFileManagerEventSpy(); + $filesystem = new Filesystem(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPreLoad::class, $spy) + ->build() + ; + + $tmpFile = $filesystem->createTmpFile(sys_get_temp_dir(), 'php'); + $tmpFileManager->load($tmpFile); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileOnLoad(): void + { + $spy = new TmpFileEventSpy(); + $filesystem = new Filesystem(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileOnLoad::class, $spy) + ->build() + ; + + $tmpFileManager->load(...[ + $filesystem->createTmpFile(sys_get_temp_dir(), 'php'), + $filesystem->createTmpFile(sys_get_temp_dir(), 'php'), + $filesystem->createTmpFile(sys_get_temp_dir(), 'php'), + ]); + + $this->assertEquals(3, $spy->getEventsCount()); + } + + public function testTmpFileManagerPostLoad(): void + { + $spy = new TmpFileManagerEventSpy(); + $filesystem = new Filesystem(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPostLoad::class, $spy) + ->build() + ; + + $tmpFile = $filesystem->createTmpFile(sys_get_temp_dir(), 'php'); + $tmpFileManager->load($tmpFile); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFilePreRemove(): void + { + $spy = new TmpFileEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFilePreRemove::class, $spy) + ->build() + ; + + $tmpFile = $tmpFileManager->create(); + $tmpFileManager->remove($tmpFile); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFilePostRemove(): void + { + $spy = new TmpFileEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFilePostRemove::class, $spy) + ->build() + ; + + $tmpFile = $tmpFileManager->create(); + $tmpFileManager->remove($tmpFile); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerPrePurge(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPrePurge::class, $spy) + ->build() + ; + + $tmpFileManager->purge(); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerPostPurge(): void + { + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPostPurge::class, $spy) + ->build() + ; + + $tmpFileManager->purge(); + + $this->assertEquals(1, $spy->getEventsCount()); + } + + public function testTmpFileManagerOnFinish(): void + { + $spy = new TmpFileManagerEventSpy(); + (new TmpFileManagerBuilder()) + ->withoutAutoPurge() + ->withEventListener(TmpFileManagerOnFinish::class, $spy) + ->build() + ; + + $this->assertEquals(1, $spy->getEventsCount()); + } +} diff --git a/tests/TmpFileMangerTest.php b/tests/TmpFileMangerTest.php new file mode 100644 index 0000000..1fb94bc --- /dev/null +++ b/tests/TmpFileMangerTest.php @@ -0,0 +1,147 @@ +build(); + + $tmpFile = $tmpFileManager->create(); + + $this->assertFileExists($tmpFile->getFilename()); + } + + public function testLoadTmpFile(): void + { + $fs = new Filesystem(); + $tmpFile = $fs->createTmpFile(sys_get_temp_dir(), 'php'); + $spy = new TmpFileManagerEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFileManagerPostLoad::class, $spy) + ->build() + ; + + try { + $tmpFileManager->load($tmpFile); + } catch (\Throwable) { + if ($fs->existsTmpFile($tmpFile)) { + $fs->removeTmpFile($tmpFile); + } + } + + $this->assertEquals(1, $spy->getTmpFilesCount()); + } + + public function testLoadTheSameTmpFile(): void + { + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/^Temp file ".+" has been already added.$/'); + + $tmpFileManager->load($tmpFileManager->create()); + } + + public function testLoadDoesNotExistTmpFile(): void + { + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/^Temp file ".+" doesn\'t exist.$/'); + + $tmpFileManager->load(new TmpFile('meow.txt')); + } + + public function testIsolateTmpFile(): void + { + $spy = new TmpFileEventSpy(); + $tmpFileManager = (new TmpFileManagerBuilder()) + ->withEventListener(TmpFilePostRemove::class, $spy) + ->build() + ; + + $tmpFileManager->isolate(function (TmpFileInterface $tmpFile): void { + $this->assertFileExists($tmpFile->getFilename()); + }); + + $this->assertEquals(1, $spy->getEventsCount()); + + foreach ($spy->getTmpFiles() as $tmpFile) { + $this->assertFileDoesNotExist($tmpFile->getFilename()); + } + } + + public function testRemoveTmpFile(): void + { + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + $tmpFile = $tmpFileManager->create(); + + $tmpFileManager->remove($tmpFile); + + $this->assertFileDoesNotExist($tmpFile->getFilename()); + } + + public function testRemoveDoesNotExistTmpFile(): void + { + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/^Temp file ".+" has been already removed.$/'); + + $tmpFileManager->remove(new TmpFile('meow.txt')); + } + + public function testRemoveDoesNotAddTmpFile(): void + { + $fs = new Filesystem(); + $tmpFile = $fs->createTmpFile(sys_get_temp_dir(), 'php'); + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/^Temp file ".+" wasn\'t create through temp file manager.$/'); + + try { + $tmpFileManager->remove($tmpFile); + } finally { + if ($fs->existsTmpFile($tmpFile)) { + $fs->removeTmpFile($tmpFile); + } + } + } + + public function testPurgeTmpFile(): void + { + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + $tmpFile = $tmpFileManager->create(); + + $tmpFileManager->purge(); + + $this->assertFileDoesNotExist($tmpFile->getFilename()); + } + + public function testPurgeTmpFileAfterLoad(): void + { + $fs = new Filesystem(); + $tmpFile = $fs->createTmpFile(sys_get_temp_dir(), 'php'); + $tmpFileManager = (new TmpFileManagerBuilder())->build(); + $tmpFileManager->load($tmpFile); + + $tmpFileManager->purge(); + + $this->assertFileDoesNotExist($tmpFile->getFilename()); + } +} From f4da75bf1e85dbcd1f31f2006ac1b4bbb4a2c06d Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 16 May 2024 20:37:23 +0300 Subject: [PATCH 08/11] Update docs --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 4322454..cdc70a9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -93,7 +93,7 @@ All temp files will be immediately removed. ## Auto-purging -By default, temp files purge automatically. The feature based on `register_shutdown_function` function and init every time when temp file manager is started. For difficult use cases you can turn off auto purging: +By default, temp files purge automatically. The feature based on `register_shutdown_function(callable $callback, mixed ...$args): void` function and init every time when temp file manager is started. For difficult use cases you can turn off auto purging: ```php Date: Thu, 16 May 2024 20:40:57 +0300 Subject: [PATCH 09/11] Fix phpcsfixer issues --- src/Filesystem/Filesystem.php | 2 +- .../GarbageCollectionHandler/Processor/SyncProcessor.php | 2 +- src/TmpFileManagerBuilder.php | 2 +- tests/Event/TmpFileEventSpy.php | 2 +- tests/Event/TmpFileManagerEventSpy.php | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index e6e191f..662eab8 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -12,7 +12,7 @@ final class Filesystem implements FilesystemInterface { private Fs $fs; - public function __construct(Fs $fs = null) + public function __construct(?Fs $fs = null) { $this->fs = $fs ?? new Fs(); } diff --git a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php index 76fc3f2..67e4bd6 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/SyncProcessor.php @@ -11,7 +11,7 @@ final class SyncProcessor implements ProcessorInterface { private Fs $fs; - public function __construct(Fs $fs = null) + public function __construct(?Fs $fs = null) { $this->fs = $fs ?? new Fs(); } diff --git a/src/TmpFileManagerBuilder.php b/src/TmpFileManagerBuilder.php index 5a0b938..f48408b 100644 --- a/src/TmpFileManagerBuilder.php +++ b/src/TmpFileManagerBuilder.php @@ -23,7 +23,7 @@ final class TmpFileManagerBuilder implements TmpFileManagerBuilderInterface private Fs $fs; private EventDispatcherInterface $eventDispatcher; - public function __construct(Fs $fs = null, EventDispatcherInterface $eventDispatcher = null) + public function __construct(?Fs $fs = null, ?EventDispatcherInterface $eventDispatcher = null) { $this->tmpFileDir = sys_get_temp_dir(); $this->tmpFilePrefix = 'php'; diff --git a/tests/Event/TmpFileEventSpy.php b/tests/Event/TmpFileEventSpy.php index 7607dde..f1b9ff5 100644 --- a/tests/Event/TmpFileEventSpy.php +++ b/tests/Event/TmpFileEventSpy.php @@ -29,7 +29,7 @@ public function __invoke(AbstractTmpFileEvent $event): void $this->tmpFiles[$event->getTmpFile()->getFilename()] = $event->getTmpFile(); } - public function getEventsCount(string $className = null): int + public function getEventsCount(?string $className = null): int { if (null === $className) { return array_sum($this->eventsCounter); diff --git a/tests/Event/TmpFileManagerEventSpy.php b/tests/Event/TmpFileManagerEventSpy.php index bd4404a..9d2dd18 100644 --- a/tests/Event/TmpFileManagerEventSpy.php +++ b/tests/Event/TmpFileManagerEventSpy.php @@ -32,7 +32,7 @@ public function __invoke(AbstractTmpFileManagerEvent $event): void $this->tmpFilesCount[$event::class] = $event->getContainer()->count(); } - public function getEventsCount(string $className = null): int + public function getEventsCount(?string $className = null): int { if (null === $className) { return array_sum($this->eventsCounter); @@ -45,7 +45,7 @@ public function getEventsCount(string $className = null): int return $this->eventsCounter[$className]; } - public function getTmpFilesCount(string $className = null): int + public function getTmpFilesCount(?string $className = null): int { if (null === $className) { return array_sum($this->tmpFilesCount); From 7612793bcb91dec5bb864d5328a1ab6257d83cc1 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 16 May 2024 20:42:46 +0300 Subject: [PATCH 10/11] Update docs --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index cdc70a9..18647be 100644 --- a/docs/index.md +++ b/docs/index.md @@ -101,7 +101,7 @@ By default, temp files purge automatically. The feature based on `register_shutd use TmpFileManager\TmpFileManagerBuilder; $tmpFileManager = (new TmpFileManagerBuilder()) - ->withoutAutoPurge('php') + ->withoutAutoPurge() ->build() ; ``` From 4c379e56f74d4d3ffb1b3c47fa6f48b744682d79 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 16 May 2024 20:49:17 +0300 Subject: [PATCH 11/11] Fix tests on windows --- .../GarbageCollectionHandler/Processor/AsyncProcessor.php | 3 --- .../Processor/AsyncProcessorTest.php | 3 +++ tests/TmpFileMangerBuilderTest.php | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php index 39be4e8..c1bf47c 100644 --- a/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php +++ b/src/Handler/GarbageCollectionHandler/Processor/AsyncProcessor.php @@ -7,9 +7,6 @@ use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; -/** - * @requires OS Linux - */ final class AsyncProcessor implements ProcessorInterface { /** diff --git a/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php b/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php index 764f7b9..eab7fdd 100644 --- a/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php +++ b/tests/Handler/GarbageCollectionHandler/Processor/AsyncProcessorTest.php @@ -8,6 +8,9 @@ use Symfony\Component\Filesystem\Filesystem as Fs; use TmpFileManager\Handler\GarbageCollectionHandler\Processor\AsyncProcessor; +/** + * @requires OS Linux + */ final class AsyncProcessorTest extends TestCase { public function testSyncProcessor(): void diff --git a/tests/TmpFileMangerBuilderTest.php b/tests/TmpFileMangerBuilderTest.php index 34b9055..55cb58a 100644 --- a/tests/TmpFileMangerBuilderTest.php +++ b/tests/TmpFileMangerBuilderTest.php @@ -17,6 +17,9 @@ final class TmpFileMangerBuilderTest extends TestCase { + /** + * @requires OS Linux + */ public function testBuildWithTmpFileDir(): void { $tmpFileManager = (new TmpFileManagerBuilder()) @@ -29,6 +32,9 @@ public function testBuildWithTmpFileDir(): void $this->assertStringStartsWith(sys_get_temp_dir(), $tmpFile->getFilename()); } + /** + * @requires OS Linux + */ public function testBuildWithTmpFilePrefix(): void { $tmpFileManager = (new TmpFileManagerBuilder())