diff --git a/src/Symfony/Component/Filesystem/CHANGELOG.md b/src/Symfony/Component/Filesystem/CHANGELOG.md index 4a0755bfe0a83..a1e219f58c704 100644 --- a/src/Symfony/Component/Filesystem/CHANGELOG.md +++ b/src/Symfony/Component/Filesystem/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.1.0 +----- + + * added `watch()` method to watch filesystem for changes + 5.0.0 ----- diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index a5539fa5eb26f..9f15a5298a192 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -14,6 +14,8 @@ use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Filesystem\Exception\InvalidArgumentException; use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Watcher\FileChangeWatcher; +use Symfony\Component\Filesystem\Watcher\INotifyWatcher; /** * Provides basic utility to manipulate the file system. @@ -695,6 +697,26 @@ public function appendToFile(string $filename, $content) } } + /** + * Watches a file or directory for any changes, and calls $callback when any changes are detected. + * + * @param mixed $path The path to watch for changes. Can be a path to a file or directory, iterator or array with paths + * @param callable $callback The callback to execute when a change is detected + * @param float $timeout The idle timeout in milliseconds after which the process will be aborted if there are no changes detected + * + * @throws \InvalidArgumentException|IOException + */ + public function watch($path, callable $callback, float $timeout = null) + { + if (\extension_loaded('inotify')) { + $watcher = new INotifyWatcher(); + } else { + $watcher = new FileChangeWatcher(); + } + + $watcher->watch($path, $callback, $timeout); + } + private function toIterable($files): iterable { return \is_array($files) || $files instanceof \Traversable ? $files : [$files]; diff --git a/src/Symfony/Component/Filesystem/Tests/Fixtures/ChangeFileResource.php b/src/Symfony/Component/Filesystem/Tests/Fixtures/ChangeFileResource.php new file mode 100644 index 0000000000000..4696d23bf01d7 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Fixtures/ChangeFileResource.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Fixtures; + +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; +use Symfony\Component\Filesystem\Watcher\Resource\ResourceInterface; + +/** + * @author Pierre du Plessis + */ +final class ChangeFileResource implements ResourceInterface +{ + private $path; + + public function __construct(string $path) + { + $this->path = $path; + } + + public function detectChanges(): array + { + return [new FileChangeEvent($this->path, FileChangeEvent::FILE_CHANGED)]; + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/Watcher/FileSystemWatchTest.php b/src/Symfony/Component/Filesystem/Tests/Watcher/FileSystemWatchTest.php new file mode 100644 index 0000000000000..61b4d7bcee403 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Watcher/FileSystemWatchTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Watcher; + +use Symfony\Component\Filesystem\Tests\FilesystemTestCase; +use Symfony\Component\Filesystem\Tests\Fixtures\ChangeFileResource; +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; +use Symfony\Component\Filesystem\Watcher\FileChangeWatcher; +use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource; +use Symfony\Component\Filesystem\Watcher\Resource\ResourceInterface; + +class FileSystemWatchTest extends FilesystemTestCase +{ + public function testWatch() + { + $workspace = $this->workspace; + + $locator = new class($workspace) { + private $workspace; + + public function __construct($workspace) + { + $this->workspace = $workspace; + } + + public function locate($path): ?ResourceInterface + { + return new ChangeFileResource($this->workspace.'/foobar.txt'); + } + }; + + $watcher = new FileChangeWatcher(); + $watcher->locator = $locator; + + $count = 0; + $watcher->watch($this->workspace, function ($file, $code) use (&$count) { + $this->assertSame($this->workspace.'/foobar.txt', $file); + $this->assertSame(FileChangeEvent::FILE_CHANGED, $code); + ++$count; + + if (2 === $count) { + return false; + } + }); + + $this->assertSame(2, $count); + } + + public function testWatchTimeout() + { + $locator = new class() { + public function locate($path): ?ResourceInterface + { + return new DirectoryResource($path); + } + }; + + $watcher = new FileChangeWatcher(); + $ref = new \ReflectionProperty($watcher, 'locator'); + $ref->setAccessible(true); + $ref->setValue($watcher, $locator); + + $start = microtime(true); + $watcher->watch($this->workspace, static function ($file, $code) { + }, 500); + + $this->assertGreaterThan(0.5, microtime(true) - $start); + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/ArrayResourceTest.php b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/ArrayResourceTest.php new file mode 100644 index 0000000000000..436aa5c592d42 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/ArrayResourceTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Watcher\Resource; + +use Symfony\Component\Filesystem\Tests\FilesystemTestCase; +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; +use Symfony\Component\Filesystem\Watcher\Resource\ArrayResource; +use Symfony\Component\Filesystem\Watcher\Resource\FileResource; + +class ArrayResourceTest extends FilesystemTestCase +{ + public function testFileChange() + { + $file = $this->workspace.'/foo.txt'; + touch($file); + + $resource = new ArrayResource([new FileResource($file)]); + + $this->assertSame([], $resource->detectChanges()); + + touch($file, time() + 1); + + $this->assertEquals([new FileChangeEvent($file, FileChangeEvent::FILE_CHANGED)], $resource->detectChanges()); + $this->assertSame([], $resource->detectChanges()); + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/DirectoryResourceTest.php b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/DirectoryResourceTest.php new file mode 100644 index 0000000000000..a22b3d1849f4f --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/DirectoryResourceTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Watcher\Resource; + +use Symfony\Component\Filesystem\Tests\FilesystemTestCase; +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; +use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource; + +class DirectoryResourceTest extends FilesystemTestCase +{ + public function testCreateFile() + { + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'foo'; + mkdir($dir); + + $resource = new DirectoryResource($dir); + + $this->assertSame([], $resource->detectChanges()); + + touch($dir.'/foo.txt'); + + $this->assertEquals([new FileChangeEvent($dir.\DIRECTORY_SEPARATOR.'foo.txt', FileChangeEvent::FILE_CREATED)], $resource->detectChanges()); + $this->assertSame([], $resource->detectChanges()); + } + + public function testDeleteFile() + { + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'foo'; + mkdir($dir); + + touch($dir.'/foo.txt'); + touch($dir.'/bar.txt'); + + $resource = new DirectoryResource($dir); + + $this->assertSame([], $resource->detectChanges()); + + unlink($dir.'/foo.txt'); + + $this->assertEquals([new FileChangeEvent($dir.\DIRECTORY_SEPARATOR.'foo.txt', FileChangeEvent::FILE_DELETED)], $resource->detectChanges()); + $this->assertSame([], $resource->detectChanges()); + } + + public function testFileChanges() + { + $dir = $this->workspace.\DIRECTORY_SEPARATOR.'foo'; + mkdir($dir); + + touch($dir.'/foo.txt'); + touch($dir.'/bar.txt'); + + $resource = new DirectoryResource($dir); + + $this->assertSame([], $resource->detectChanges()); + + touch($dir.'/foo.txt', time() + 1); + + $this->assertEquals([new FileChangeEvent($dir.\DIRECTORY_SEPARATOR.'foo.txt', FileChangeEvent::FILE_CHANGED)], $resource->detectChanges()); + $this->assertSame([], $resource->detectChanges()); + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/FileResourceTest.php b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/FileResourceTest.php new file mode 100644 index 0000000000000..c901145b2b11a --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/FileResourceTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Watcher\Resource; + +use Symfony\Component\Filesystem\Tests\FilesystemTestCase; +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; +use Symfony\Component\Filesystem\Watcher\Resource\FileResource; + +class FileResourceTest extends FilesystemTestCase +{ + public function testFileChanges() + { + $file = $this->workspace.'/foo.txt'; + touch($file); + + $resource = new FileResource($file); + + $this->assertSame([], $resource->detectChanges()); + + touch($file, time() + 1); + + $this->assertEquals([new FileChangeEvent($file, FileChangeEvent::FILE_CHANGED)], $resource->detectChanges()); + $this->assertSame([], $resource->detectChanges()); + } +} diff --git a/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/Locator/FileResourceLocatorTest.php b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/Locator/FileResourceLocatorTest.php new file mode 100644 index 0000000000000..206ae7c0a05cf --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Watcher/Resource/Locator/FileResourceLocatorTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests\Watcher\Resource\Locator; + +use Symfony\Component\Filesystem\Tests\FilesystemTestCase; +use Symfony\Component\Filesystem\Watcher\Resource\ArrayResource; +use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource; +use Symfony\Component\Filesystem\Watcher\Resource\FileResource; +use Symfony\Component\Filesystem\Watcher\Resource\Locator\FileResourceLocator; + +class FileResourceLocatorTest extends FilesystemTestCase +{ + public function testLocateIterator() + { + $locator = new FileResourceLocator(); + + $path = new \ArrayIterator([$this->createFile('foo.txt')]); + + $this->assertEquals(new ArrayResource([new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'foo.txt')]), $locator->locate($path)); + } + + public function testLocateSplFileInfo() + { + $locator = new FileResourceLocator(); + + $path = new \SplFileInfo($this->createFile('foo.txt')); + + $this->assertEquals(new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'foo.txt'), $locator->locate($path)); + } + + public function testFilePath() + { + $locator = new FileResourceLocator(); + + $path = $this->createFile('foo.txt'); + + $this->assertEquals(new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'foo.txt'), $locator->locate($path)); + } + + public function testGlob() + { + $locator = new FileResourceLocator(); + + $this->createFile('bar.txt'); + $this->createFile('foo.txt'); + + $this->assertEquals( + new ArrayResource([new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'bar.txt'), new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'foo.txt')]), + $locator->locate($this->workspace.\DIRECTORY_SEPARATOR.'*.txt') + ); + } + + public function testArray() + { + $locator = new FileResourceLocator(); + + $path = [$this->createFile('foo.txt')]; + + $this->assertEquals(new ArrayResource([new FileResource($this->workspace.\DIRECTORY_SEPARATOR.'foo.txt')]), $locator->locate($path)); + } + + public function testDirectory() + { + $locator = new FileResourceLocator(); + + $dir = $this->createDirecty('foobar'); + + $this->assertEquals(new DirectoryResource($this->workspace.\DIRECTORY_SEPARATOR.'foobar'), $locator->locate($dir)); + } + + private function createFile(string $file) + { + $fullPath = $this->workspace.\DIRECTORY_SEPARATOR.$file; + touch($fullPath); + + return $fullPath; + } + + private function createDirecty(string $dir) + { + $fullPath = $this->workspace.\DIRECTORY_SEPARATOR.$dir; + + mkdir($fullPath, 0777, true); + + return $fullPath; + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/FileChangeEvent.php b/src/Symfony/Component/Filesystem/Watcher/FileChangeEvent.php new file mode 100644 index 0000000000000..593fb3fb76857 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/FileChangeEvent.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher; + +/** + * @author Pierre du Plessis + */ +final class FileChangeEvent +{ + public const FILE_CHANGED = 1; + public const FILE_DELETED = 2; + public const FILE_CREATED = 3; + + private $file; + + private $event; + + public function __construct(string $file, int $event) + { + $this->file = $file; + $this->event = $event; + } + + public function getFile(): string + { + return $this->file; + } + + public function getEvent(): int + { + return $this->event; + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/FileChangeWatcher.php b/src/Symfony/Component/Filesystem/Watcher/FileChangeWatcher.php new file mode 100644 index 0000000000000..864681e91ff56 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/FileChangeWatcher.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher; + +use Symfony\Component\Filesystem\Watcher\Resource\Locator\FileResourceLocator; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class FileChangeWatcher implements WatcherInterface +{ + public $locator; + + public function __construct() + { + $this->locator = new FileResourceLocator(); + } + + public function watch($path, callable $callback, float $timeout = null): void + { + $resource = $this->locator->locate($path); + + if (!$resource) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid path to watch.', \gettype($path))); + } + + $run = true; + $start = microtime(true); + + while ($run) { + if ($changes = $resource->detectChanges()) { + foreach ($changes as $change) { + $run = false !== $callback($change->getFile(), $change->getEvent()); + } + + $start = microtime(true); + } + + if (null !== $timeout && ($timeout / 1000) <= (microtime(true) - $start)) { + break; + } + + sleep(1); + } + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/INotifyWatcher.php b/src/Symfony/Component/Filesystem/Watcher/INotifyWatcher.php new file mode 100644 index 0000000000000..0d413eb844e43 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/INotifyWatcher.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class INotifyWatcher implements WatcherInterface +{ + public function watch($path, callable $callback, float $timeout = null): void + { + $inotifyInit = inotify_init(); + + if (false === $inotifyInit) { + throw new IOException('Unable initialize inotify.', 0, null, $path); + } + + stream_set_blocking($inotifyInit, false); + + $isDir = is_dir($path); + $watchers = []; + + if ($isDir) { + $watchers[] = inotify_add_watch($inotifyInit, $path, IN_CREATE | IN_DELETE | IN_MODIFY); + + foreach ($this->scanPath("$path/*") as $path) { + $watchers[] = inotify_add_watch($inotifyInit, $path, IN_CREATE | IN_DELETE | IN_MODIFY); + } + } else { + $watchers[] = inotify_add_watch($inotifyInit, $path, IN_MODIFY); + } + + try { + $read = [$inotifyInit]; + $write = null; + $except = null; + $tvSec = null === $timeout ? null : 0; + $tvUsec = null === $timeout ? null : $timeout * 1000; + + while (true) { + if (0 === stream_select($read, $write, $except, $tvSec, $tvUsec)) { + $read = [$inotifyInit]; + break; + } + + $events = inotify_read($inotifyInit); + + if (false === $events) { + continue; + } + + foreach ($events as $event) { + $code = null; + switch ($event['mask']) { + case IN_CREATE: + $code = FileChangeEvent::FILE_CREATED; + break; + case IN_DELETE: + $code = FileChangeEvent::FILE_DELETED; + break; + case IN_MODIFY: + $code = FileChangeEvent::FILE_CHANGED; + break; + } + + if (false === $callback(($isDir ? $path : '').$event['name'], $code)) { + break; + } + } + } + } finally { + foreach ($watchers as $watchId) { + inotify_rm_watch($inotifyInit, $watchId); + } + + fclose($inotifyInit); + } + } + + private function scanPath($path): iterable + { + foreach (glob($path, GLOB_ONLYDIR) as $directory) { + yield $directory; + yield from $this->scanPath("$directory/*"); + } + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/Resource/ArrayResource.php b/src/Symfony/Component/Filesystem/Watcher/Resource/ArrayResource.php new file mode 100644 index 0000000000000..3091c34110855 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/Resource/ArrayResource.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher\Resource; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class ArrayResource implements ResourceInterface +{ + /** + * @var ResourceInterface[] + */ + private $resources; + + public function __construct(array $resources) + { + $this->resources = $resources; + } + + public function detectChanges(): array + { + $events = []; + + foreach ($this->resources as $resource) { + if ($changed = $resource->detectChanges()) { + $events = array_merge($events, $changed); + } + } + + return $events; + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/Resource/DirectoryResource.php b/src/Symfony/Component/Filesystem/Watcher/Resource/DirectoryResource.php new file mode 100644 index 0000000000000..e4c52649e1965 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/Resource/DirectoryResource.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher\Resource; + +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class DirectoryResource implements ResourceInterface +{ + private $dir; + + /** + * @var FileResource[] + */ + private $files; + + public function __construct(string $dir) + { + $this->dir = $dir; + + $this->files = $this->getFiles(); + } + + public function detectChanges(): array + { + $events = []; + + $currentFiles = $this->getFiles(); + + // Check if any files has been added + foreach (array_keys($currentFiles) as $path) { + if (!isset($this->files[$path])) { + $this->files = $currentFiles; + + $events[] = new FileChangeEvent($path, FileChangeEvent::FILE_CREATED); + } + } + + // Check if any files has been deleted + foreach (array_keys($this->files) as $file) { + if (!isset($currentFiles[$file])) { + $this->files = $currentFiles; + + $events[] = new FileChangeEvent($file, FileChangeEvent::FILE_DELETED); + } + } + + // Check for any changes in files + foreach ($this->files as $file) { + if ($event = $file->detectChanges()) { + $events = array_merge($events, $event); + } + } + + return $events; + } + + private function getFiles(): array + { + $files = []; + + /** @var \SplFileInfo $file */ + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir, \RecursiveDirectoryIterator::SKIP_DOTS)) as $file) { + $path = $file->getRealPath(); + $files[$path] = new FileResource($path); + } + + return $files; + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/Resource/FileResource.php b/src/Symfony/Component/Filesystem/Watcher/Resource/FileResource.php new file mode 100644 index 0000000000000..e463340206d3f --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/Resource/FileResource.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher\Resource; + +use Symfony\Component\Filesystem\Watcher\FileChangeEvent; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class FileResource implements ResourceInterface +{ + private $file; + + private $lastModified; + + public function __construct(string $file) + { + $this->file = $file; + $this->lastModified = filemtime($file); + } + + public function detectChanges(): array + { + if ($this->isModified()) { + $this->updateModifiedTime(); + + return [new FileChangeEvent($this->file, FileChangeEvent::FILE_CHANGED)]; + } + + return []; + } + + private function isModified(): bool + { + clearstatcache(false, $this->file); + + return $this->lastModified < filemtime($this->file); + } + + private function updateModifiedTime(): void + { + $this->lastModified = filemtime($this->file); + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/Resource/Locator/FileResourceLocator.php b/src/Symfony/Component/Filesystem/Watcher/Resource/Locator/FileResourceLocator.php new file mode 100644 index 0000000000000..79ffb3c631e64 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/Resource/Locator/FileResourceLocator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher\Resource\Locator; + +use Symfony\Component\Filesystem\Watcher\Resource\ArrayResource; +use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource; +use Symfony\Component\Filesystem\Watcher\Resource\FileResource; +use Symfony\Component\Filesystem\Watcher\Resource\ResourceInterface; +use Symfony\Component\Finder\Finder; + +/** + * @author Pierre du Plessis + * + * @internal + */ +final class FileResourceLocator +{ + public function locate($path): ?ResourceInterface + { + if ($path instanceof Finder || $path instanceof \Iterator) { + $path = iterator_to_array($path); + } + + if ($path instanceof \SplFileInfo) { + $path = $path->getRealPath(); + } + + if (\is_array($path)) { + return new ArrayResource(array_map([$this, 'locate'], $path)); + } + + if (\is_string($path)) { + if (is_dir($path)) { + return new DirectoryResource($path); + } + + $paths = glob($path, \defined('GLOB_BRACE') ? GLOB_BRACE : 0); + + if (1 === \count($paths)) { + return new FileResource($paths[0]); + } + + return new ArrayResource(array_map(function ($path) { + return new FileResource($path); + }, $paths)); + } + + return null; + } +} diff --git a/src/Symfony/Component/Filesystem/Watcher/Resource/ResourceInterface.php b/src/Symfony/Component/Filesystem/Watcher/Resource/ResourceInterface.php new file mode 100644 index 0000000000000..ec5069d0bf731 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/Resource/ResourceInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher\Resource; + +/** + * @author Pierre du Plessis + */ +interface ResourceInterface +{ + public function detectChanges(): array; +} diff --git a/src/Symfony/Component/Filesystem/Watcher/WatcherInterface.php b/src/Symfony/Component/Filesystem/Watcher/WatcherInterface.php new file mode 100644 index 0000000000000..c58304252f2f2 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Watcher/WatcherInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Watcher; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * @author Pierre du Plessis + */ +interface WatcherInterface +{ + /** + * @param mixed $path The path to watch for changes. Can be a path to a file or directory, iterator or array with paths + * @param callable $callback The callback to execute when a change is detected + * @param float $timeout The idle timeout in milliseconds after which the process will be aborted if there are no changes detected + */ + public function watch($path, callable $callback, float $timeout = null): void; +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy