-
-
Notifications
You must be signed in to change notification settings - Fork 176
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip * Fix styling * cleanup * phpstan * Fix event * Fix event watcher * Fix facade * add events * Remove useless stubs * Fix styling * add some sanity tests * wip * add artisan shorthand * allow passing either a string or array * correct return type * flip arguments for consistency * tidy - remove unused class properties * remove unnecessary space escape * add optional arg to make the process persistent * improvements - ChildProcess instances can be used to interact with a process - get, all and restart are piped up * Fix styling * Update src/ChildProcess.php Co-authored-by: Simon Hamp <[email protected]> * feedback - tidy cwd default path * stub out php command tests * fix - tests after upstream merge * add php convenience method * wip - refactor * add phpdoc for facade methods * Update src/Facades/ChildProcess.php Co-authored-by: Simon Hamp <[email protected]> * remove exploding string commands * fix - return a fresh instance from the facade each time --------- Co-authored-by: simonhamp <[email protected]> Co-authored-by: gwleuverink <[email protected]> Co-authored-by: Willem Leuverink <[email protected]>
- Loading branch information
1 parent
2c89fba
commit fcdcc06
Showing
10 changed files
with
372 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
<?php | ||
|
||
namespace Native\Laravel; | ||
|
||
use Native\Laravel\Client\Client; | ||
|
||
class ChildProcess | ||
{ | ||
public readonly int $pid; | ||
|
||
public readonly string $alias; | ||
|
||
public readonly array $cmd; | ||
|
||
public readonly ?string $cwd; | ||
|
||
public readonly ?array $env; | ||
|
||
public readonly bool $persistent; | ||
|
||
public function __construct(protected Client $client) {} | ||
|
||
public function get(?string $alias = null): ?static | ||
{ | ||
$alias = $alias ?? $this->alias; | ||
|
||
$process = $this->client->get("child-process/get/{$alias}")->json(); | ||
|
||
if (! $process) { | ||
return null; | ||
} | ||
|
||
return $this->fromRuntimeProcess($process); | ||
} | ||
|
||
public function all(): array | ||
{ | ||
$processes = $this->client->get('child-process/')->json(); | ||
|
||
if (empty($processes)) { | ||
return []; | ||
} | ||
|
||
$hydrated = []; | ||
|
||
foreach ($processes as $alias => $process) { | ||
$hydrated[$alias] = (new static($this->client)) | ||
->fromRuntimeProcess($process); | ||
} | ||
|
||
return $hydrated; | ||
} | ||
|
||
public function start( | ||
string|array $cmd, | ||
string $alias, | ||
?string $cwd = null, | ||
?array $env = null, | ||
bool $persistent = false | ||
): static { | ||
|
||
$process = $this->client->post('child-process/start', [ | ||
'alias' => $alias, | ||
'cmd' => (array) $cmd, | ||
'cwd' => $cwd ?? base_path(), | ||
'env' => $env, | ||
'persistent' => $persistent, | ||
])->json(); | ||
|
||
return $this->fromRuntimeProcess($process); | ||
} | ||
|
||
public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self | ||
{ | ||
$cmd = [PHP_BINARY, ...(array) $cmd]; | ||
|
||
return $this->start($cmd, $alias, env: $env, persistent: $persistent); | ||
} | ||
|
||
public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self | ||
{ | ||
$cmd = ['artisan', ...(array) $cmd]; | ||
|
||
return $this->php($cmd, $alias, env: $env, persistent: $persistent); | ||
} | ||
|
||
public function stop(?string $alias = null): void | ||
{ | ||
$this->client->post('child-process/stop', [ | ||
'alias' => $alias ?? $this->alias, | ||
])->json(); | ||
} | ||
|
||
public function restart(?string $alias = null): ?static | ||
{ | ||
$process = $this->client->post('child-process/restart', [ | ||
'alias' => $alias ?? $this->alias, | ||
])->json(); | ||
|
||
if (! $process) { | ||
return null; | ||
} | ||
|
||
return $this->fromRuntimeProcess($process); | ||
} | ||
|
||
public function message(string $message, ?string $alias = null): static | ||
{ | ||
$this->client->post('child-process/message', [ | ||
'alias' => $alias ?? $this->alias, | ||
'message' => $message, | ||
])->json(); | ||
|
||
return $this; | ||
} | ||
|
||
protected function fromRuntimeProcess($process): static | ||
{ | ||
if (isset($process['pid'])) { | ||
$this->pid = $process['pid']; | ||
} | ||
|
||
foreach ($process['settings'] as $key => $value) { | ||
$this->{$key} = $value; | ||
} | ||
|
||
return $this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Native\Laravel\Events\ChildProcess; | ||
|
||
use Illuminate\Broadcasting\Channel; | ||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||
use Illuminate\Foundation\Events\Dispatchable; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class ErrorReceived implements ShouldBroadcast | ||
{ | ||
use Dispatchable, SerializesModels; | ||
|
||
public function __construct(public string $alias, public mixed $data) {} | ||
|
||
public function broadcastOn() | ||
{ | ||
return [ | ||
new Channel('nativephp'), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Native\Laravel\Events\ChildProcess; | ||
|
||
use Illuminate\Broadcasting\Channel; | ||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||
use Illuminate\Foundation\Events\Dispatchable; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class MessageReceived implements ShouldBroadcast | ||
{ | ||
use Dispatchable, SerializesModels; | ||
|
||
public function __construct(public string $alias, public mixed $data) {} | ||
|
||
public function broadcastOn() | ||
{ | ||
return [ | ||
new Channel('nativephp'), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Native\Laravel\Events\ChildProcess; | ||
|
||
use Illuminate\Broadcasting\Channel; | ||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||
use Illuminate\Foundation\Events\Dispatchable; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class ProcessExited implements ShouldBroadcast | ||
{ | ||
use Dispatchable, SerializesModels; | ||
|
||
public function __construct(public string $alias, public int $code) {} | ||
|
||
public function broadcastOn() | ||
{ | ||
return [ | ||
new Channel('nativephp'), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Native\Laravel\Events\ChildProcess; | ||
|
||
use Illuminate\Broadcasting\Channel; | ||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||
use Illuminate\Foundation\Events\Dispatchable; | ||
use Illuminate\Queue\SerializesModels; | ||
|
||
class ProcessSpawned implements ShouldBroadcast | ||
{ | ||
use Dispatchable, SerializesModels; | ||
|
||
public function __construct(public string $alias) {} | ||
|
||
public function broadcastOn() | ||
{ | ||
return [ | ||
new Channel('nativephp'), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace Native\Laravel\Facades; | ||
|
||
use Illuminate\Support\Facades\Facade; | ||
use Native\Laravel\ChildProcess as Implement; | ||
|
||
/** | ||
* @method static \Native\Laravel\ChildProcess[] all() | ||
* @method static \Native\Laravel\ChildProcess get(string $alias = null) | ||
* @method static \Native\Laravel\ChildProcess message(string $message, string $alias = null) | ||
* @method static \Native\Laravel\ChildProcess restart(string $alias = null) | ||
* @method static \Native\Laravel\ChildProcess start(string|array $cmd, string $alias, string $cwd = null, array $env = null, bool $persistent = false) | ||
* @method static \Native\Laravel\ChildProcess php(string|array $cmd, string $alias, array $env = null, bool $persistent = false) | ||
* @method static \Native\Laravel\ChildProcess artisan(string|array $cmd, string $alias, array $env = null, bool $persistent = false) | ||
* @method static void stop(string $alias = null) | ||
*/ | ||
class ChildProcess extends Facade | ||
{ | ||
protected static function getFacadeAccessor() | ||
{ | ||
self::clearResolvedInstance(Implement::class); | ||
|
||
return Implement::class; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<?php | ||
|
||
use Illuminate\Http\Client\Request; | ||
use Illuminate\Support\Facades\Http; | ||
use Mockery; | ||
Check warning on line 5 in tests/ChildProcess/ChildProcessTest.php
|
||
use Native\Laravel\ChildProcess as ChildProcessImplement; | ||
use Native\Laravel\Client\Client; | ||
use Native\Laravel\Facades\ChildProcess; | ||
|
||
beforeEach(function () { | ||
Http::fake(); | ||
|
||
$mock = Mockery::mock(ChildProcessImplement::class, [resolve(Client::class)]) | ||
->makePartial() | ||
->shouldAllowMockingProtectedMethods(); | ||
|
||
$this->instance(ChildProcessImplement::class, $mock->allows([ | ||
'fromRuntimeProcess' => $mock, | ||
])); | ||
}); | ||
|
||
it('can start a child process', function () { | ||
ChildProcess::start('foo bar', 'some-alias', 'path/to/dir', ['baz' => 'zah']); | ||
|
||
Http::assertSent(function (Request $request) { | ||
return $request->url() === 'http://localhost:4000/api/child-process/start' && | ||
$request['alias'] === 'some-alias' && | ||
$request['cmd'] === ['foo bar'] && | ||
$request['cwd'] === 'path/to/dir' && | ||
$request['env'] === ['baz' => 'zah']; | ||
}); | ||
}); | ||
|
||
it('can start a php command', function () { | ||
ChildProcess::php("-r 'sleep(5);'", 'some-alias', ['baz' => 'zah']); | ||
|
||
Http::assertSent(function (Request $request) { | ||
return $request->url() === 'http://localhost:4000/api/child-process/start' && | ||
$request['alias'] === 'some-alias' && | ||
$request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"] && | ||
$request['cwd'] === base_path() && | ||
$request['env'] === ['baz' => 'zah']; | ||
}); | ||
}); | ||
|
||
it('can start a artisan command', function () { | ||
ChildProcess::artisan('foo:bar --verbose', 'some-alias', ['baz' => 'zah']); | ||
|
||
Http::assertSent(function (Request $request) { | ||
return $request->url() === 'http://localhost:4000/api/child-process/start' && | ||
$request['alias'] === 'some-alias' && | ||
$request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar --verbose'] && | ||
$request['cwd'] === base_path() && | ||
$request['env'] === ['baz' => 'zah']; | ||
}); | ||
}); | ||
|
||
it('accepts either a string or a array as start command argument', function () { | ||
ChildProcess::start('foo bar', 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === ['foo bar']); | ||
|
||
ChildProcess::start(['foo', 'baz'], 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === ['foo', 'baz']); | ||
}); | ||
|
||
it('accepts either a string or a array as php command argument', function () { | ||
ChildProcess::php("-r 'sleep(5);'", 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"]); | ||
|
||
ChildProcess::php(['-r', "'sleep(5);'"], 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, '-r', "'sleep(5);'"]); | ||
}); | ||
|
||
it('accepts either a string or a array as artisan command argument', function () { | ||
ChildProcess::artisan('foo:bar', 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar']); | ||
|
||
ChildProcess::artisan(['foo:baz'], 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:baz']); | ||
}); | ||
|
||
it('sets the cwd to the base path if none was given', function () { | ||
ChildProcess::start(['foo', 'bar'], 'some-alias', cwd: 'path/to/dir'); | ||
Http::assertSent(fn (Request $request) => $request['cwd'] === 'path/to/dir'); | ||
|
||
ChildProcess::start(['foo', 'bar'], 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['cwd'] === base_path()); | ||
}); | ||
|
||
it('can stop a child process', function () { | ||
ChildProcess::stop('some-alias'); | ||
|
||
Http::assertSent(function (Request $request) { | ||
return $request->url() === 'http://localhost:4000/api/child-process/stop' && | ||
$request['alias'] === 'some-alias'; | ||
}); | ||
}); | ||
|
||
it('can send messages to a child process', function () { | ||
ChildProcess::message('some-message', 'some-alias'); | ||
|
||
Http::assertSent(function (Request $request) { | ||
return $request->url() === 'http://localhost:4000/api/child-process/message' && | ||
$request['alias'] === 'some-alias' && | ||
$request['message'] === 'some-message'; | ||
}); | ||
}); | ||
|
||
it('can mark a process as persistent', function () { | ||
ChildProcess::start('foo bar', 'some-alias', persistent: true); | ||
Http::assertSent(fn (Request $request) => $request['persistent'] === true); | ||
}); | ||
|
||
it('can mark a php command as persistent', function () { | ||
ChildProcess::php("-r 'sleep(5);'", 'some-alias', persistent: true); | ||
Http::assertSent(fn (Request $request) => $request['persistent'] === true); | ||
}); | ||
|
||
it('can mark a artisan command as persistent', function () { | ||
ChildProcess::artisan('foo:bar', 'some-alias', persistent: true); | ||
Http::assertSent(fn (Request $request) => $request['persistent'] === true); | ||
}); | ||
|
||
it('marks the process as non-persistent by default', function () { | ||
ChildProcess::start('foo bar', 'some-alias'); | ||
Http::assertSent(fn (Request $request) => $request['persistent'] === false); | ||
}); |