Skip to content

Commit 7ef4b10

Browse files
committed
Add watch method to watch filesystem for changes
1 parent e19db43 commit 7ef4b10

17 files changed

+762
-0
lines changed

src/Symfony/Component/Filesystem/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.4.0
5+
-----
6+
7+
* added `watch()` method to watch filesystem for changes
8+
49
4.3.0
510
-----
611

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
1515
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
1616
use Symfony\Component\Filesystem\Exception\IOException;
17+
use Symfony\Component\Filesystem\Watcher\FileChangeWatcher;
18+
use Symfony\Component\Filesystem\Watcher\INotifyWatcher;
1719

1820
/**
1921
* Provides basic utility to manipulate the file system.
@@ -726,6 +728,25 @@ public function appendToFile($filename, $content)
726728
}
727729
}
728730

731+
/**
732+
* Watches a file or directory for any changes, and calls $callback when any changes are detected.
733+
*
734+
* @param mixed $path The path to watch for changes. Can be a path to a file or directory, iterator or array with paths
735+
* @param callable $callback The callback to execute when a change is detected
736+
*
737+
* @throws \InvalidArgumentException
738+
*/
739+
public function watch($path, callable $callback)
740+
{
741+
if (\extension_loaded('inotify')) {
742+
$watcher = new INotifyWatcher();
743+
} else {
744+
$watcher = new FileChangeWatcher();
745+
}
746+
747+
$watcher->watch($path, $callback);
748+
}
749+
729750
private function toIterable($files): iterable
730751
{
731752
return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Fixtures;
13+
14+
use Symfony\Component\Filesystem\Watcher\FileChangeEvent;
15+
use Symfony\Component\Filesystem\Watcher\Resource\ResourceInterface;
16+
17+
class ChangeFileResource implements ResourceInterface
18+
{
19+
private $path;
20+
21+
public function __construct(string $path)
22+
{
23+
$this->path = $path;
24+
}
25+
26+
public function detectChanges(): array
27+
{
28+
return [new FileChangeEvent($this->path, FileChangeEvent::FILE_CHANGED)];
29+
}
30+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Watcher;
13+
14+
use Symfony\Component\Filesystem\Tests\FilesystemTestCase;
15+
use Symfony\Component\Filesystem\Tests\Fixtures\ChangeFileResource;
16+
use Symfony\Component\Filesystem\Watcher\FileChangeEvent;
17+
use Symfony\Component\Filesystem\Watcher\FileChangeWatcher;
18+
use Symfony\Component\Filesystem\Watcher\Resource\ResourceInterface;
19+
20+
class FileSystemWatchTest extends FilesystemTestCase
21+
{
22+
public function testWatch()
23+
{
24+
$workspace = $this->workspace;
25+
26+
$locator = new class($workspace) {
27+
private $workspace;
28+
29+
public function __construct($workspace)
30+
{
31+
$this->workspace = $workspace;
32+
}
33+
34+
public function locate($path): ?ResourceInterface
35+
{
36+
return new ChangeFileResource($this->workspace.'/foobar.txt');
37+
}
38+
};
39+
40+
$watcher = new FileChangeWatcher();
41+
$ref = new \ReflectionProperty($watcher, 'locator');
42+
$ref->setAccessible(true);
43+
$ref->setValue($watcher, $locator);
44+
45+
$count = 0;
46+
$watcher->watch($this->workspace, function ($file, $code) use (&$count) {
47+
$this->assertSame($this->workspace.'/foobar.txt', $file);
48+
$this->assertSame(FileChangeEvent::FILE_CHANGED, $code);
49+
++$count;
50+
51+
if (2 === $count) {
52+
return false;
53+
}
54+
});
55+
56+
$this->assertSame(2, $count);
57+
}
58+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Watcher\Resource;
13+
14+
use Symfony\Component\Filesystem\Tests\FilesystemTestCase;
15+
use Symfony\Component\Filesystem\Watcher\FileChangeEvent;
16+
use Symfony\Component\Filesystem\Watcher\Resource\ArrayResource;
17+
use Symfony\Component\Filesystem\Watcher\Resource\FileResource;
18+
19+
class ArrayResourceTest extends FilesystemTestCase
20+
{
21+
public function testFileChange()
22+
{
23+
$file = $this->workspace.'/foo.txt';
24+
touch($file);
25+
26+
$resource = new ArrayResource([new FileResource($file)]);
27+
28+
$this->assertSame([], $resource->detectChanges());
29+
30+
touch($file, time() + 1);
31+
32+
$this->assertEquals([new FileChangeEvent($file, FileChangeEvent::FILE_CHANGED)], $resource->detectChanges());
33+
$this->assertSame([], $resource->detectChanges());
34+
}
35+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Watcher\Resource;
13+
14+
use Symfony\Component\Filesystem\Tests\FilesystemTestCase;
15+
use Symfony\Component\Filesystem\Watcher\FileChangeEvent;
16+
use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource;
17+
18+
class DirectoryResourceTest extends FilesystemTestCase
19+
{
20+
public function testCreateFile()
21+
{
22+
$dir = $this->workspace.'/foo';
23+
mkdir($dir);
24+
25+
$resource = new DirectoryResource($dir);
26+
27+
$this->assertSame([], $resource->detectChanges());
28+
29+
touch($dir.'/foo.txt');
30+
31+
$this->assertEquals([new FileChangeEvent($dir.'/foo.txt', FileChangeEvent::FILE_CREATED)], $resource->detectChanges());
32+
$this->assertSame([], $resource->detectChanges());
33+
}
34+
35+
public function testDeleteFile()
36+
{
37+
$dir = $this->workspace.'/foo';
38+
mkdir($dir);
39+
40+
touch($dir.'/foo.txt');
41+
touch($dir.'/bar.txt');
42+
43+
$resource = new DirectoryResource($dir);
44+
45+
$this->assertSame([], $resource->detectChanges());
46+
47+
unlink($dir.'/foo.txt');
48+
49+
$this->assertEquals([new FileChangeEvent($dir.'/foo.txt', FileChangeEvent::FILE_DELETED)], $resource->detectChanges());
50+
$this->assertSame([], $resource->detectChanges());
51+
}
52+
53+
public function testFileChanges()
54+
{
55+
$dir = $this->workspace.'/foo';
56+
mkdir($dir);
57+
58+
touch($dir.'/foo.txt');
59+
touch($dir.'/bar.txt');
60+
61+
$resource = new DirectoryResource($dir);
62+
63+
$this->assertSame([], $resource->detectChanges());
64+
65+
touch($dir.'/foo.txt', time() + 1);
66+
67+
$this->assertEquals([new FileChangeEvent($dir.'/foo.txt', FileChangeEvent::FILE_CHANGED)], $resource->detectChanges());
68+
$this->assertSame([], $resource->detectChanges());
69+
}
70+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Watcher\Resource;
13+
14+
use Symfony\Component\Filesystem\Tests\FilesystemTestCase;
15+
use Symfony\Component\Filesystem\Watcher\FileChangeEvent;
16+
use Symfony\Component\Filesystem\Watcher\Resource\FileResource;
17+
18+
class FileResourceTest extends FilesystemTestCase
19+
{
20+
public function testFileChanges()
21+
{
22+
$file = $this->workspace.'/foo.txt';
23+
touch($file);
24+
25+
$resource = new FileResource($file);
26+
27+
$this->assertSame([], $resource->detectChanges());
28+
29+
touch($file, time() + 1);
30+
31+
$this->assertEquals([new FileChangeEvent($file, FileChangeEvent::FILE_CHANGED)], $resource->detectChanges());
32+
$this->assertSame([], $resource->detectChanges());
33+
}
34+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Watcher\Resource\Locator;
13+
14+
use Symfony\Component\Filesystem\Tests\FilesystemTestCase;
15+
use Symfony\Component\Filesystem\Watcher\Resource\ArrayResource;
16+
use Symfony\Component\Filesystem\Watcher\Resource\DirectoryResource;
17+
use Symfony\Component\Filesystem\Watcher\Resource\FileResource;
18+
use Symfony\Component\Filesystem\Watcher\Resource\Locator\FileResourceLocator;
19+
20+
class FileResourceLocatorTest extends FilesystemTestCase
21+
{
22+
public function testLocateIterator()
23+
{
24+
$locator = new FileResourceLocator();
25+
26+
$path = new \ArrayIterator([$this->createFile('foo.txt')]);
27+
28+
$this->assertEquals(new ArrayResource([new FileResource($this->workspace.'/foo.txt')]), $locator->locate($path));
29+
}
30+
31+
public function testLocateSplFileInfo()
32+
{
33+
$locator = new FileResourceLocator();
34+
35+
$path = new \SplFileInfo($this->createFile('foo.txt'));
36+
37+
$this->assertEquals(new FileResource($this->workspace.'/foo.txt'), $locator->locate($path));
38+
}
39+
40+
public function testFilePath()
41+
{
42+
$locator = new FileResourceLocator();
43+
44+
$path = $this->createFile('foo.txt');
45+
46+
$this->assertEquals(new FileResource($this->workspace.'/foo.txt'), $locator->locate($path));
47+
}
48+
49+
public function testGlob()
50+
{
51+
$locator = new FileResourceLocator();
52+
53+
$this->createFile('bar.txt');
54+
$this->createFile('foo.txt');
55+
56+
$this->assertEquals(
57+
new ArrayResource([new FileResource($this->workspace.'/bar.txt'), new FileResource($this->workspace.'/foo.txt')]),
58+
$locator->locate($this->workspace.'/*.txt')
59+
);
60+
}
61+
62+
public function testArray()
63+
{
64+
$locator = new FileResourceLocator();
65+
66+
$path = [$this->createFile('foo.txt')];
67+
68+
$this->assertEquals(new ArrayResource([new FileResource($this->workspace.'/foo.txt')]), $locator->locate($path));
69+
}
70+
71+
public function testDirectory()
72+
{
73+
$locator = new FileResourceLocator();
74+
75+
$dir = $this->createDirecty('foobar');
76+
77+
$this->assertEquals(new DirectoryResource($this->workspace.'/foobar'), $locator->locate($dir));
78+
}
79+
80+
private function createFile(string $file)
81+
{
82+
$fullPath = $this->workspace.'/'.$file;
83+
touch($fullPath);
84+
85+
return $fullPath;
86+
}
87+
88+
private function createDirecty(string $dir)
89+
{
90+
$fullPath = $this->workspace.'/'.$dir;
91+
92+
mkdir($fullPath, 0777, true);
93+
94+
return $fullPath;
95+
}
96+
}

0 commit comments

Comments
 (0)
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