Skip to content

Commit b11e4e6

Browse files
authored
Merge pull request #68 from seregazhuk/use-fswatch
Use different fs watchers
2 parents e61c321 + 36c4ef9 commit b11e4e6

26 files changed

Lines changed: 519 additions & 252 deletions

.github/workflows/security.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
uses: shivammathur/setup-php@v2
3737
with:
3838
php-version: ${{ matrix.php }}
39-
extensions: dom, sockets, grpc, curl
39+
extensions: pcntl
4040

4141
- name: Check Out Code
4242
uses: actions/checkout@v4
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: 'Tests with Chokidar'
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths-ignore:
8+
- '.gitignore'
9+
- 'CHANGELOG.md'
10+
- 'LICENSE'
11+
- 'README.md'
12+
13+
pull_request:
14+
paths-ignore:
15+
- '.gitignore'
16+
- 'CHANGELOG.md'
17+
- 'LICENSE'
18+
- 'README.md'
19+
20+
jobs:
21+
tests:
22+
timeout-minutes: 5
23+
name: Tests with Chokidar (PHP ${{ matrix.php }}, OS ${{ matrix.os }})
24+
25+
runs-on: ${{ matrix.os }}
26+
27+
strategy:
28+
matrix:
29+
os: [ubuntu-latest]
30+
php: [8.1, 8.2, 8.3, 8.4]
31+
32+
env:
33+
extensions: xdebug
34+
35+
steps:
36+
- name: Check Out Code
37+
uses: actions/checkout@v4
38+
39+
- name: Remove fswatch
40+
run: sudo apt-get remove fswatch
41+
42+
- name: Setup node
43+
uses: actions/setup-node@v6
44+
45+
- name: Install chokidar
46+
run: npm install chokidar
47+
48+
- name: Setup PHP ${{ matrix.php }}
49+
uses: shivammathur/setup-php@v2
50+
with:
51+
php-version: ${{ matrix.php }}
52+
extensions: pcntl
53+
54+
- name: Validate composer.json and composer.lock
55+
run: composer validate --strict
56+
57+
- name: Install dependencies with composer
58+
uses: ramsey/composer-install@v3
59+
with:
60+
dependency-versions: ${{ matrix.dependencies }}
61+
62+
- name: Validate lowest dependencies
63+
if: matrix.dependencies == 'lowest' && matrix.php == '8.1'
64+
env:
65+
COMPOSER_POOL_OPTIMIZER: 0
66+
run: vendor/bin/validate-prefer-lowest
67+
68+
- name: Run tests with Phpunit
69+
run: |
70+
XDEBUG_MODE=coverage php vendor/bin/phpunit
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Tests'
1+
name: 'Tests with FsWatch'
22

33
on:
44
push:
@@ -18,28 +18,31 @@ on:
1818
- 'README.md'
1919

2020
jobs:
21-
tests:
22-
name: Tests (PHP ${{ matrix.php }}, OS ${{ matrix.os }})
21+
tests-fswatch:
22+
timeout-minutes: 5
23+
name: Tests with FsWatch (PHP ${{ matrix.php }}, OS ${{ matrix.os }})
2324

2425
runs-on: ${{ matrix.os }}
2526

2627
strategy:
2728
matrix:
28-
os: [ubuntu-latest]
29+
os: [macos-latest ]
2930
php: [8.1, 8.2, 8.3, 8.4]
30-
3131
env:
3232
extensions: xdebug
3333

3434
steps:
3535
- name: Check Out Code
3636
uses: actions/checkout@v4
3737

38+
- name: Install fswatch
39+
run: brew install fswatch
40+
3841
- name: Setup PHP ${{ matrix.php }}
3942
uses: shivammathur/setup-php@v2
4043
with:
4144
php-version: ${{ matrix.php }}
42-
extensions: dom, sockets, grpc, curl ${{ matrix.extensions-suffix }}
45+
extensions: pcntl
4346

4447
- name: Validate composer.json and composer.lock
4548
run: composer validate --strict
@@ -57,4 +60,4 @@ jobs:
5760

5861
- name: Run tests with Phpunit
5962
run: |
60-
XDEBUG_MODE=coverage php vendor/bin/phpunit --testsuite unit
63+
php vendor/bin/phpunit

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ PHP-watcher does not require any additional changes to your code or method of
3535
* [Default executable](#default-executable)
3636
* [Gracefully reloading down your script](#gracefully-reloading-down-your-script)
3737
* [Automatic restart](#automatic-restart)
38+
* [Performance](#performance)
3839
* [Spinner](#spinner)
3940

4041
## Installation
@@ -55,9 +56,6 @@ composer require seregazhuk/php-watcher --dev
5556
```
5657
Locally installed you can run it with `vendor/bin/php-watcher`.
5758

58-
Under the hood, to watch filesystem changes, PHP-watcher uses JavaScript package [chokidar](https://github.com/paulmillr/chokidar).
59-
At first run it will check and install it if required.
60-
6159
## Usage
6260

6361
All the examples assume you've installed the package globally. If you opted for the local installation prepend `vendor/bin/` everywhere where `php-watcher` is mentioned.
@@ -236,9 +234,28 @@ script crashes PHP-watcher will notify you about that.
236234

237235
![app exit](images/exit.svg)
238236

237+
## Performance
238+
239+
The watcher can use different strategies to monitor your file system changes. Under the hood it
240+
detects the environment and chooses the best suitable strategy.
241+
242+
### Fswatch (OSX only)
243+
244+
[FsWatch](https://github.com/emcrisostomo/fswatch) is a cross-platform (Linux,Mac,Windows) file change monitor that will automatically
245+
use the platforms native functionality when possible. Under the hood the filesystem notifies us
246+
when any changes occur. Currently, it [doesn't work correctly on Linux](https://github.com/emcrisostomo/fswatch/issues/247).
247+
If your system is OSx and has fswatch installed, this strategy will be used.
248+
249+
**Has not been extensively tested.**
250+
251+
### Chokidar
252+
253+
[Chokidar](https://github.com/paulmillr/chokidar) is a JavaScript package for watching file and directory changes.
254+
At first run the watcher will check if Node.js is available in the system. If it is, it will install chokidar into the project.
255+
239256
## Spinner
240257

241-
By default the watcher outputs a nice spinner which indicates that the process is running
258+
By default, the watcher outputs a nice spinner which indicates that the process is running
242259
and watching your files. But if your system doesn't support ansi coded the watcher
243260
will try to detect it and disable the spinner. Or you can always disable the spinner
244261
manually with option '--no-spinner':

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@
3131
"react/child-process": "^0.6.1",
3232
"react/event-loop": "^1.1",
3333
"react/stream": "^1.0.0",
34-
"spatie/file-system-watcher": "^1.2",
34+
"seregazhuk/reactphp-fswatch": "^1.1.1",
3535
"symfony/console": "^6.0",
3636
"symfony/finder": "^6.0",
37+
"symfony/process": "^6.0",
3738
"symfony/yaml": "^6.0"
3839
},
3940
"autoload": {

phpunit.xml.dist

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@
66
convertNoticesToExceptions="true"
77
convertWarningsToExceptions="true"
88
processIsolation="false"
9-
stopOnFailure="true"
9+
stopOnFailure="false"
1010
bootstrap="vendor/autoload.php">
1111
<testsuites>
1212
<testsuite name="Common">
13-
<directory suffix="Test.php">tests</directory>
13+
<directory suffix="Test.php">./tests</directory>
1414
</testsuite>
1515
</testsuites>
1616
<filter>
1717
<whitelist processUncoveredFilesFromWhitelist="true">
1818
<directory suffix=".php">src</directory>
19-
<exclude>
20-
<file>src/Filesystem/watcher.php</file>
21-
</exclude>
2219
</whitelist>
2320
</filter>
2421
</phpunit>

src/Filesystem/ChangesListener.php

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace seregazhuk\PhpWatcher\Filesystem\ChangesListener;
4+
5+
use seregazhuk\PhpWatcher\Config\WatchList;
6+
7+
interface ChangesListenerInterface
8+
{
9+
public function start(WatchList $watchList): void;
10+
11+
public function onChange(callable $callback): void;
12+
13+
public function stop(): void;
14+
15+
public function getName(): string;
16+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace seregazhuk\PhpWatcher\Filesystem\ChangesListener;
6+
7+
use Evenement\EventEmitter;
8+
use React\EventLoop\LoopInterface;
9+
use React\EventLoop\TimerInterface;
10+
use seregazhuk\PhpWatcher\Config\WatchList;
11+
use Symfony\Component\Process\ExecutableFinder;
12+
use Symfony\Component\Process\Process;
13+
14+
final class ChokidarChangesListener extends EventEmitter implements ChangesListenerInterface
15+
{
16+
private const INTERVAL = 0.15;
17+
18+
private ?Process $process = null;
19+
20+
private ?TimerInterface $timer = null;
21+
22+
public function __construct(private readonly LoopInterface $loop) {}
23+
24+
public function start(WatchList $watchList): void
25+
{
26+
$command = [
27+
(new ExecutableFinder)->find('node'),
28+
realpath(__DIR__.'/../../../bin/file-watcher.js'),
29+
json_encode($watchList->getPaths()),
30+
json_encode($watchList->getIgnored()),
31+
json_encode($watchList->getFileExtensions()),
32+
];
33+
34+
$this->process = new Process(command: $command);
35+
$this->process->start();
36+
37+
$this->timer = $this->loop->addPeriodicTimer(
38+
self::INTERVAL,
39+
function (): void {
40+
$output = $this->process->getIncrementalOutput();
41+
if ($output !== '') {
42+
$this->emit('change');
43+
}
44+
}
45+
);
46+
}
47+
48+
public function onChange(callable $callback): void
49+
{
50+
$this->on('change', $callback);
51+
}
52+
53+
public function stop(): void
54+
{
55+
if ($this->process instanceof Process && $this->process->isRunning()) {
56+
$this->process->stop();
57+
}
58+
59+
if ($this->timer instanceof TimerInterface) {
60+
$this->loop->cancelTimer($this->timer);
61+
}
62+
}
63+
64+
public function getName(): string
65+
{
66+
return 'chokidar';
67+
}
68+
}

0 commit comments

Comments
 (0)