Skip to content

Commit fb81a15

Browse files
committed
initial commit for php watcher
0 parents  commit fb81a15

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

composer.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "cydrickn/php-watcher",
3+
"description": "Simple file watcher",
4+
"type": "library",
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-4": {
8+
"Cydrickn\\PHPWatcher\\": "src/"
9+
}
10+
},
11+
"authors": [
12+
{
13+
"name": "Cydrick Nonog",
14+
"email": "cydrick.dev@gmail.com"
15+
}
16+
],
17+
"require": {}
18+
}

src/Enum/ChangeType.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Cydrickn\PHPWatcher\Enum;
4+
5+
enum ChangeType: int
6+
{
7+
case NEW = 1;
8+
case UPDATED = 2;
9+
case DELETED = 3;
10+
}

src/Watcher.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
namespace Cydrickn\PHPWatcher;
4+
5+
use Cydrickn\PHPWatcher\Enum\ChangeType;
6+
use RecursiveDirectoryIterator;
7+
use RecursiveTreeIterator;
8+
use Swoole\Timer;
9+
10+
class Watcher
11+
{
12+
private array $files = [];
13+
private array $changes = [];
14+
private bool $initialized = false;
15+
private bool $checking = false;
16+
17+
private array $excludeFiles = [];
18+
private array $excludeDirs = [];
19+
private mixed $handler;
20+
21+
public function __construct(private array $watchFor, array $excludes, callable $handler, private int $interval = 1000)
22+
{
23+
foreach ($excludes as $exclude) {
24+
if (is_dir($exclude)) {
25+
$this->excludeDirs[] = $exclude;
26+
} else {
27+
$this->excludeFiles[] = $exclude;
28+
}
29+
}
30+
$this->handler = $handler;
31+
}
32+
33+
34+
public function addChange(string $filename, ChangeType $type): void
35+
{
36+
$this->changes[] = ['name' => $filename, 'type' => $type, 'data' => filemtime($filename)];
37+
}
38+
39+
public function checkFile(string $file): void
40+
{
41+
if (!array_key_exists($file, $this->files) && file_exists($file)) {
42+
$this->addChange($file, ChangeType::NEW);
43+
return;
44+
}
45+
46+
if (array_key_exists($file, $this->files) && !file_exists($file)) {
47+
$this->addChange($file, ChangeType::DELETED);
48+
return;
49+
}
50+
51+
if (!(array_key_exists($file, $this->files) && file_exists($file))) {
52+
return;
53+
}
54+
55+
$data = $this->files[$file];
56+
$checkData = filemtime($file);
57+
58+
if ($checkData != $data) {
59+
$this->addChange($file, ChangeType::UPDATED);
60+
}
61+
}
62+
63+
public function clearChanges(): void
64+
{
65+
$this->changes = [];
66+
}
67+
68+
public function commit(): void
69+
{
70+
$totalChanges = count($this->changes);
71+
foreach ($this->changes as $change) {
72+
if ($change['type'] === ChangeType::DELETED) {
73+
unset($this->files[$change['name']]);
74+
continue;
75+
}
76+
77+
$this->files[$change['name']] = $change['data'];
78+
}
79+
80+
if ($totalChanges > 0 && $this->initialized) {
81+
call_user_func($this->handler, $this->changes);
82+
}
83+
84+
$this->clearChanges();
85+
}
86+
87+
public function checkChanges(): void
88+
{
89+
if ($this->checking) {
90+
return;
91+
}
92+
93+
$this->checking = true;
94+
95+
foreach ($this->watchFor as $watchFor) {
96+
if (is_dir($watchFor)) {
97+
$allFiles = new RecursiveTreeIterator(new RecursiveDirectoryIterator(__DIR__, RecursiveDirectoryIterator::SKIP_DOTS));
98+
foreach ($allFiles as $file) {
99+
$filename = trim(str_replace(['|', ' ', '~', '\\'], '', $file), '-');
100+
$filename = is_dir($filename) ? $filename . '/' : $filename;
101+
102+
if ((is_file($filename) && in_array($filename, $this->excludeFiles)) || (is_dir($filename) && in_array($filename, $this->excludeDirs))) {
103+
continue;
104+
}
105+
106+
foreach ($this->excludeDirs as $dir) {
107+
if (str_starts_with($filename, $dir)) {
108+
continue 2;
109+
}
110+
}
111+
$this->checkFile($filename);
112+
}
113+
continue;
114+
}
115+
$this->checkFile($watchFor);
116+
}
117+
118+
$this->commit();
119+
120+
$this->checking = false;
121+
$this->initialized = true;
122+
}
123+
124+
public function tick(?callable $handler = null): void
125+
{
126+
if ($handler !== null) {
127+
call_user_func($handler, [$this, 'checkChanges'], $this->interval);
128+
return;
129+
}
130+
131+
if (class_exists(Timer::class)) {
132+
Timer::tick($this->interval, function () {
133+
$this->checkChanges();
134+
});
135+
} else {
136+
$this->tick(function ($checkChanges, $interval) {
137+
do {
138+
call_user_func($checkChanges);
139+
sleep($interval);
140+
} while (true);
141+
});
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)