Skip to content

Commit 2b63667

Browse files
Add file watcher (#20)
* [ch] enable sync between volumes - In order to update the docker container with a file change, a docker rebuild is required. This will stop the need to run a rebuild. * [ch] set docker timeout to `0` - The default timeout for docker `stop` and `restart` is `10` seconds. Please see [Docker Docs](https://docs.docker.com/compose/reference/restart/). * [ch] remove cmd to make entry file executable - This has been recently pointed out that this is not required as it is a php file. * [ch] add best practice to `ENTRYPOINT` - It is considered more performant to pass `ENTRYPOINT` and `CMD` an array of the required command. * [f] revert `ENTRYPOINT` to `ServerStub.php` * [ch] implement restart of app on changes - Separate demo and test with env specific Docker file and add script to allow for app to be restarted where there are any changes (create,delete,modify) made whilst deloping the application. This is more proof of concept as this will only work on a Linux OS with `inotify-tools` installed, but as a proof of concept it _seems_ to work gracefully. * [ch] Add CLI flags - Allow `h`, `--host`, `p`, `--port` to be passed to start the server. * [a] using GoLang to watch local dev - This will start up the ReactPHP server in a child process within the GoLang script. Killing the script will kill the child process as intended allowing for a better development environment. * [ch] improve initial output code - refs #17
1 parent 585486e commit 2b63667

File tree

11 files changed

+222
-9
lines changed

11 files changed

+222
-9
lines changed

.build/react.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
"os/exec"
7+
"fmt"
8+
"io/ioutil"
9+
"encoding/json"
10+
11+
"github.com/fsnotify/fsnotify"
12+
)
13+
14+
var (
15+
settings struct {
16+
ServerPath string `json:"server_path"`
17+
Port string `json:"port"`
18+
Host string `json:"host"`
19+
DirectoriesToWatch []string `json:"directories_to_watch"`
20+
}
21+
ProcessID int = 0
22+
)
23+
24+
func clearScreen() {
25+
c := exec.Command("clear")
26+
c.Stdout = os.Stdout
27+
c.Run()
28+
}
29+
30+
func startApp(pathToServer string, port string, host string) {
31+
if port != "" {
32+
port = "--port=" + port
33+
}
34+
35+
if host != "" {
36+
host = "--host=" + host
37+
}
38+
39+
systemWithoutOutput("php", pathToServer, port, host)
40+
}
41+
42+
func stopApp() {
43+
killCommand := fmt.Sprintf("kill %d", ProcessID)
44+
systemWithoutOutput("sh","-c", killCommand)
45+
}
46+
47+
func restartApp(serverPath string, port string, host string) {
48+
clearScreen()
49+
fmt.Println("Restarting app")
50+
stopApp()
51+
startApp(serverPath, port, host)
52+
}
53+
54+
func systemWithoutOutput(cmd string, arg ...string) {
55+
command := exec.Command(cmd, arg...)
56+
command.Stdout = os.Stdout
57+
err := command.Start()
58+
59+
if err != nil {
60+
log.Fatal(err)
61+
}
62+
63+
ProcessID = command.Process.Pid
64+
}
65+
66+
func getDevServerConfig () {
67+
jsonFile, err := ioutil.ReadFile("./reactor.config.json")
68+
if err != nil {
69+
log.Fatal(err)
70+
}
71+
72+
json.Unmarshal(jsonFile, &settings)
73+
}
74+
75+
func initConsole (directoriesBeingWatch []string) {
76+
fmt.Println("Running React App")
77+
fmt.Println("Current directories/files being watched:")
78+
for _, directory := range directoriesBeingWatch {
79+
fmt.Println("\t",directory)
80+
}
81+
fmt.Println("")
82+
}
83+
84+
func main() {
85+
getDevServerConfig()
86+
87+
var (
88+
serverPath string = settings.ServerPath
89+
directoriesToWatch []string = settings.DirectoriesToWatch
90+
port string = settings.Port
91+
host string = settings.Host
92+
)
93+
94+
startApp(serverPath, port, host)
95+
initConsole(directoriesToWatch)
96+
97+
watcher, err := fsnotify.NewWatcher()
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
defer watcher.Close()
102+
103+
done := make(chan bool)
104+
105+
go func() {
106+
for {
107+
select {
108+
case event := <-watcher.Events:
109+
if event.Op&fsnotify.Write == fsnotify.Write {
110+
restartApp(serverPath, port, host)
111+
}
112+
case err := <-watcher.Errors:
113+
log.Println("error:", err)
114+
}
115+
}
116+
}()
117+
118+
for _, directory := range directoriesToWatch {
119+
err = watcher.Add(directory)
120+
if err != nil {
121+
log.Fatal(err)
122+
}
123+
}
124+
125+
<-done
126+
}

.tools/docker-watch

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
3+
composer start
4+
5+
echo "Watching \`./example\` and \`./src\`"
6+
7+
while true; do
8+
filename=$(inotifywait -qre modify -e create -e move -e delete --format %f {./example,./src} )
9+
printf "Restarted due to change in %s" $filename|awk '{split($0,a,"__"); print a[1]}'
10+
docker restart -t 0 reactive_slim >> /dev/null
11+
done

.tools/local-watch

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
3+
php ./example/app-prod-mode.php &
4+
5+
echo "Watching \`./example\` and \`./src\`"
6+
7+
while true; do
8+
filename=$(inotifywait -qre modify -e create -e move -e delete --format %f {./example,./src} )
9+
printf "Restarted due to change in %s" $filename|awk '{split($0,a,"__"); print a[1]}'
10+
kill `ps aux|grep app-prod-mode.php | \
11+
head -n1 | \
12+
awk -d='\t' '{print $2}'`
13+
php ./example/app-prod-mode.php &
14+
done

.tools/reactor

2.32 MB
Binary file not shown.

.tools/test-docker

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/sh
2+
3+
# Build & run container
4+
docker build -t reactive_slim_test -f ./Dockerfile.test .
5+
docker run -d -p 1351:1351 --name reactive_tests reactive_slim_test
6+
7+
# run tests
8+
./bin/phpspec r
9+
./bin/phpunit -c ./test/phpunit.xml.dist
10+
11+
# tidy up
12+
docker kill reactive_tests
13+
docker rm -f reactive_tests

Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ RUN apk update
77
RUN apk add --update zlib-dev
88
RUN docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) iconv
99
RUN rm -rf /tmp/*
10-
RUN chmod +x ./test/Integration/ServerStub.php
1110

12-
EXPOSE 1351
11+
EXPOSE 1337
1312

14-
ENTRYPOINT php ./test/Integration/ServerStub.php
13+
ENTRYPOINT ["php", "./example/app-prod-mode.php"]

Dockerfile.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM php:7.0-alpine
2+
3+
COPY . /var/app
4+
WORKDIR /var/app
5+
6+
RUN apk update
7+
RUN apk add --update zlib-dev
8+
RUN docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) iconv
9+
RUN rm -rf /tmp/*
10+
11+
EXPOSE 1351
12+
13+
ENTRYPOINT ["php", "./test/Integration/ServerStub.php"]

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ After creating your [Slim Instance](https://www.slimframework.com/), pass it to
2424

2525
_Please see the [examples](/example) for more information or run `php ./example/app-dev-mode.php`._
2626

27+
If are you are running via PHP locally, you are able to pass the following flags to customise both the host and the port:
28+
29+
`-h 0.0.0.0` or `--host=0.0.0.0` and `-p 8686` or `--port=8686`
30+
31+
### Restarting your ReactApp
32+
33+
There are 3 ways to restart your application (only tested on Linux - Solus, kernel 4.9.30-29.lts currently):
34+
35+
- `sh ./.tools/local-watch` runs via local PHP installation (requires `inotify-tools` to be installed via package manager)
36+
- `sh ./.tools/reactor` run via local PHP installation and requires the `reactor.config.json` config
37+
- `sh ./.tools/docker-watch` run the app within a Docker container (requires `inotify-tools` to be installed via package manager)
38+
2739
### Extra options
2840

2941
`#withHost(<string>)` - The default host URL is `0.0.0.0` but is overridden by passing a string as the parameter

composer.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,15 @@
5555
"sort-packages": true
5656
},
5757
"scripts": {
58-
"build": "docker build -t nigelgreenway:reactive-slim .",
59-
"start": "docker run -d -p 1351:1351 --name reactive_slim nigelgreenway:reactive-slim",
60-
"restart": "docker restart reactive_slim",
61-
"stop": "docker stop reactive_slim",
62-
"test": "composer start && phpspec r && phpunit -c ./test/phpunit.xml.dist ./test && composer stop",
58+
"build": "docker build -t reactive-slim -f Dockerfile .",
59+
"start": "docker run -v `pwd`:/var/app -d -p 1337:1337 --name reactive_slim reactive-slim",
60+
"watch": "./.tools/docker-watch",
61+
"restart": "docker restart -t 0 reactive_slim",
62+
"stop": "docker stop -t 0 reactive_slim",
63+
"test": "sh ./.tools/test-docker",
6364
"check-style": "phpcs -p --standard=psr2-override.xml --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src",
6465
"fix-style": "phpcbf -p --standard=psr2-override.xml --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src",
65-
"clean": "rm -r ./{vendor,bin} && docker stop reactive_slim && docker rm reactive_slim",
66+
"clean": "rm -r ./{vendor,bin} && docker stop -t 0 reactive_slim && docker rm reactive_slim",
6667
"post-install-cmd": [
6768
"captainhook install",
6869
"composer build"

reactor.config.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"server_path": "./example/app-dev-mode.php",
3+
"port": "1337",
4+
"host": "0.0.0.0",
5+
"directories_to_watch": [
6+
"./example/",
7+
"./src/"
8+
]
9+
}

src/Server.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function __construct(
4848
string $directoryPath = null
4949
) {
5050
$this->isAValidDirectory($directoryPath);
51+
$this->setDefaults();
5152

5253
$this->webRoot = $directoryPath;
5354
$this->slimInstance = $slimInstance;
@@ -192,6 +193,20 @@ private function initialiseReactPHP()
192193
$this->server = new HttpServer($socketServer);
193194
}
194195

196+
/**
197+
* @return void
198+
*/
199+
private function setDefaults()
200+
{
201+
$options = getopt('p::h::', ['port::', 'host::']);
202+
203+
$port = $options['p'] ?? $options['port'] ?? 1337;
204+
$this->setPort((int) $port);
205+
206+
$host = $options['h'] ?? $options['host'] ?? '0.0.0.0';
207+
$this->setHost($host);
208+
}
209+
195210
/**
196211
* @param string|null $directoryPath
197212
*

0 commit comments

Comments
 (0)