Skip to content

Commit ac6ac0e

Browse files
committed
New process component for switching between host php and php via docker
1 parent 11911b2 commit ac6ac0e

9 files changed

+451
-0
lines changed

src/Process/DockerProcessFactory.php

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Process;
6+
7+
use PhpSchool\PhpWorkshop\Utils\Collection;
8+
use Symfony\Component\Process\ExecutableFinder;
9+
use Symfony\Component\Process\Process;
10+
use PhpSchool\PhpWorkshop\Utils\System;
11+
12+
final class DockerProcessFactory implements ProcessFactory
13+
{
14+
private ExecutableFinder $executableFinder;
15+
private string $basePath;
16+
private string $projectName;
17+
private string $composerCacheDir;
18+
19+
public function __construct(
20+
string $basePath,
21+
string $projectName,
22+
string $composerCacheDir,
23+
ExecutableFinder $executableFinder = null
24+
) {
25+
$this->executableFinder = $executableFinder ?? new ExecutableFinder();
26+
$this->basePath = $basePath;
27+
$this->projectName = $projectName;
28+
$this->composerCacheDir = $composerCacheDir;
29+
}
30+
31+
public function create(ProcessInput $processInput): Process
32+
{
33+
$mounts = [];
34+
if ($processInput->getExecutable() === 'composer') {
35+
//we need to mount a volume for composer cache
36+
$mounts[] = $this->composerCacheDir . ':/root/.composer/cache';
37+
}
38+
39+
$env = array_map(function ($key, $value) {
40+
return sprintf('-e %s=%s', $key, $value);
41+
}, array_keys($processInput->getEnv()), $processInput->getEnv());
42+
43+
return new Process(
44+
[...$this->baseComposeCommand($mounts, $env), 'runtime', $processInput->getExecutable(), ...$processInput->getArgs()],
45+
$this->basePath,
46+
['SOLUTION' => $processInput->getWorkingDirectory()],
47+
$processInput->getInput(),
48+
10
49+
);
50+
}
51+
52+
/**
53+
* @param array<string> $mounts
54+
* @param array<string> $env
55+
* @return array<string>
56+
*/
57+
private function baseComposeCommand(array $mounts, array $env): array
58+
{
59+
$dockerPath = $this->executableFinder->find('docker');
60+
if ($dockerPath === null) {
61+
throw ProcessNotFoundException::fromExecutable('docker');
62+
}
63+
64+
return [
65+
$dockerPath,
66+
'compose',
67+
'-p',
68+
$this->projectName,
69+
'-f',
70+
'.docker/runtime/docker-compose.yml',
71+
'run',
72+
'--rm',
73+
...$env,
74+
'-w',
75+
'/solution',
76+
...array_merge(...array_map(fn ($mount) => ['-v', $mount], $mounts)),
77+
];
78+
}
79+
}

src/Process/HostProcessFactory.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Process;
6+
7+
use PhpSchool\PhpWorkshop\Utils\Collection;
8+
use Symfony\Component\Process\ExecutableFinder;
9+
use Symfony\Component\Process\Process;
10+
11+
final class HostProcessFactory implements ProcessFactory
12+
{
13+
private ExecutableFinder $executableFinder;
14+
15+
public function __construct(ExecutableFinder $executableFinder = null)
16+
{
17+
$this->executableFinder = $executableFinder ?? new ExecutableFinder();
18+
}
19+
20+
21+
public function create(ProcessInput $processInput): Process
22+
{
23+
$executablePath = $this->executableFinder->find($processInput->getExecutable());
24+
25+
if ($executablePath === null) {
26+
throw ProcessNotFoundException::fromExecutable($processInput->getExecutable());
27+
}
28+
29+
return new Process(
30+
[$executablePath, ...$processInput->getArgs()],
31+
$processInput->getWorkingDirectory(),
32+
$this->getDefaultEnv() + $processInput->getEnv(),
33+
$processInput->getInput(),
34+
10,
35+
);
36+
}
37+
38+
/**
39+
* @return array<string, false>
40+
*/
41+
private function getDefaultEnv(): array
42+
{
43+
$env = array_map(fn () => false, $_ENV);
44+
$env + array_map(fn () => false, $_SERVER);
45+
46+
return $env;
47+
}
48+
}

src/Process/ProcessFactory.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Process;
6+
7+
use PhpSchool\PhpWorkshop\Utils\ArrayObject;
8+
use PhpSchool\PhpWorkshop\Utils\Collection;
9+
use Symfony\Component\Process\Process;
10+
11+
interface ProcessFactory
12+
{
13+
public function create(ProcessInput $processInput): Process;
14+
}

src/Process/ProcessInput.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Process;
4+
5+
class ProcessInput
6+
{
7+
/**
8+
* @param list<string> $args
9+
* @param array<string, string> $env
10+
*/
11+
public function __construct(
12+
private string $executable,
13+
private array $args,
14+
private string $workingDirectory,
15+
private array $env,
16+
private ?string $input = null
17+
) {
18+
}
19+
20+
public function getExecutable(): string
21+
{
22+
return $this->executable;
23+
}
24+
25+
/**
26+
* @return list<string>
27+
*/
28+
public function getArgs(): array
29+
{
30+
return $this->args;
31+
}
32+
33+
public function getWorkingDirectory(): string
34+
{
35+
return $this->workingDirectory;
36+
}
37+
38+
/**
39+
* @return array<string, string>
40+
*/
41+
public function getEnv(): array
42+
{
43+
return $this->env;
44+
}
45+
46+
public function getInput(): ?string
47+
{
48+
return $this->input;
49+
}
50+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshop\Process;
4+
5+
class ProcessNotFoundException extends \RuntimeException
6+
{
7+
public static function fromExecutable(string $executable): self
8+
{
9+
return new self(sprintf('Could not find executable: "%s"', $executable));
10+
}
11+
}
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshopTest\Process;
4+
5+
use PhpSchool\PhpWorkshop\Process\DockerProcessFactory;
6+
use PhpSchool\PhpWorkshop\Process\ProcessInput;
7+
use PhpSchool\PhpWorkshop\Process\ProcessNotFoundException;
8+
use PHPUnit\Framework\TestCase;
9+
use Symfony\Component\Process\ExecutableFinder;
10+
11+
class DockerProcessFactoryTest extends TestCase
12+
{
13+
public function testCreateThrowsExceptionIfDockerNotFound(): void
14+
{
15+
static::expectException(ProcessNotFoundException::class);
16+
17+
$finder = $this->createMock(ExecutableFinder::class);
18+
$finder->expects($this->once())
19+
->method('find')
20+
->with('docker')
21+
->willReturn(null);
22+
23+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
24+
$input = new ProcessInput('composer', [], __DIR__, []);
25+
26+
$factory->create($input);
27+
}
28+
29+
public function testCreate(): void
30+
{
31+
$finder = $this->createMock(ExecutableFinder::class);
32+
$finder->expects($this->once())
33+
->method('find')
34+
->with('docker')
35+
->willReturn('/usr/local/bin/docker');
36+
37+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
38+
$input = new ProcessInput('php', [], __DIR__, []);
39+
40+
$process = $factory->create($input);
41+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' 'runtime' 'php'", $process->getCommandLine());
42+
static::assertSame('/docker-dir', $process->getWorkingDirectory());
43+
}
44+
45+
public function testCreateMountsComposerCacheDirIfExecutableIsComposer(): void
46+
{
47+
$finder = $this->createMock(ExecutableFinder::class);
48+
$finder->expects($this->once())
49+
->method('find')
50+
->with('docker')
51+
->willReturn('/usr/local/bin/docker');
52+
53+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
54+
$input = new ProcessInput('composer', [], __DIR__, []);
55+
56+
$process = $factory->create($input);
57+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' '-v' '/composer/cache/dir:/root/.composer/cache' 'runtime' 'composer'", $process->getCommandLine());
58+
static::assertSame('/docker-dir', $process->getWorkingDirectory());
59+
}
60+
61+
public function testCreateWithArgs(): void
62+
{
63+
$finder = $this->createMock(ExecutableFinder::class);
64+
$finder->expects($this->once())
65+
->method('find')
66+
->with('docker')
67+
->willReturn('/usr/local/bin/docker');
68+
69+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
70+
$input = new ProcessInput('php', ['one', 'two'], __DIR__, []);
71+
72+
$process = $factory->create($input);
73+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' 'runtime' 'php' 'one' 'two'", $process->getCommandLine());
74+
static::assertSame('/docker-dir', $process->getWorkingDirectory());
75+
}
76+
77+
public function testCreateWithEnv(): void
78+
{
79+
$finder = $this->createMock(ExecutableFinder::class);
80+
$finder->expects($this->once())
81+
->method('find')
82+
->with('docker')
83+
->willReturn('/usr/local/bin/docker');
84+
85+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
86+
$input = new ProcessInput('php', ['one', 'two'], __DIR__, ['SOME_VAR' => 'value']);
87+
88+
$process = $factory->create($input);
89+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-e SOME_VAR=value' '-w' '/solution' 'runtime' 'php' 'one' 'two'", $process->getCommandLine());
90+
static::assertSame('/docker-dir', $process->getWorkingDirectory());
91+
}
92+
93+
public function testWithInput(): void
94+
{
95+
$finder = $this->createMock(ExecutableFinder::class);
96+
$finder->expects($this->once())
97+
->method('find')
98+
->with('docker')
99+
->willReturn('/usr/local/bin/docker');
100+
101+
$factory = new DockerProcessFactory('/composer-dir', 'php8appreciate', '/composer/cache/dir', $finder);
102+
$input = new ProcessInput('php', [], __DIR__, [], 'someinput');
103+
104+
$process = $factory->create($input);
105+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' 'runtime' 'php'", $process->getCommandLine());
106+
static::assertSame('someinput', $process->getInput());
107+
}
108+
109+
public function testSolutionDirectoryIsPassedAsEnvVar(): void
110+
{
111+
$finder = $this->createMock(ExecutableFinder::class);
112+
$finder->expects($this->once())
113+
->method('find')
114+
->with('docker')
115+
->willReturn('/usr/local/bin/docker');
116+
117+
$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
118+
$input = new ProcessInput('php', ['one', 'two'], __DIR__, ['SOME_VAR' => 'value']);
119+
120+
$process = $factory->create($input);
121+
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-e SOME_VAR=value' '-w' '/solution' 'runtime' 'php' 'one' 'two'", $process->getCommandLine());
122+
static::assertSame('/docker-dir', $process->getWorkingDirectory());
123+
static::assertSame(['SOLUTION' => __DIR__], $process->getEnv());
124+
}
125+
}

0 commit comments

Comments
 (0)