Skip to content

Commit

Permalink
New process component for switching between host php and php via docker
Browse files Browse the repository at this point in the history
  • Loading branch information
AydinHassan committed May 5, 2024
1 parent 11911b2 commit ac6ac0e
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 0 deletions.
79 changes: 79 additions & 0 deletions src/Process/DockerProcessFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace PhpSchool\PhpWorkshop\Process;

use PhpSchool\PhpWorkshop\Utils\Collection;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use PhpSchool\PhpWorkshop\Utils\System;

final class DockerProcessFactory implements ProcessFactory
{
private ExecutableFinder $executableFinder;
private string $basePath;
private string $projectName;
private string $composerCacheDir;

public function __construct(
string $basePath,
string $projectName,
string $composerCacheDir,
ExecutableFinder $executableFinder = null
) {
$this->executableFinder = $executableFinder ?? new ExecutableFinder();
$this->basePath = $basePath;
$this->projectName = $projectName;
$this->composerCacheDir = $composerCacheDir;
}

public function create(ProcessInput $processInput): Process
{
$mounts = [];
if ($processInput->getExecutable() === 'composer') {
//we need to mount a volume for composer cache
$mounts[] = $this->composerCacheDir . ':/root/.composer/cache';
}

$env = array_map(function ($key, $value) {
return sprintf('-e %s=%s', $key, $value);
}, array_keys($processInput->getEnv()), $processInput->getEnv());

return new Process(
[...$this->baseComposeCommand($mounts, $env), 'runtime', $processInput->getExecutable(), ...$processInput->getArgs()],
$this->basePath,
['SOLUTION' => $processInput->getWorkingDirectory()],
$processInput->getInput(),
10
);
}

/**
* @param array<string> $mounts
* @param array<string> $env
* @return array<string>
*/
private function baseComposeCommand(array $mounts, array $env): array
{
$dockerPath = $this->executableFinder->find('docker');
if ($dockerPath === null) {
throw ProcessNotFoundException::fromExecutable('docker');
}

return [
$dockerPath,
'compose',
'-p',
$this->projectName,
'-f',
'.docker/runtime/docker-compose.yml',
'run',
'--rm',
...$env,
'-w',
'/solution',
...array_merge(...array_map(fn ($mount) => ['-v', $mount], $mounts)),
];
}
}
48 changes: 48 additions & 0 deletions src/Process/HostProcessFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace PhpSchool\PhpWorkshop\Process;

use PhpSchool\PhpWorkshop\Utils\Collection;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

final class HostProcessFactory implements ProcessFactory
{
private ExecutableFinder $executableFinder;

public function __construct(ExecutableFinder $executableFinder = null)
{
$this->executableFinder = $executableFinder ?? new ExecutableFinder();
}


public function create(ProcessInput $processInput): Process
{
$executablePath = $this->executableFinder->find($processInput->getExecutable());

if ($executablePath === null) {
throw ProcessNotFoundException::fromExecutable($processInput->getExecutable());
}

return new Process(
[$executablePath, ...$processInput->getArgs()],
$processInput->getWorkingDirectory(),
$this->getDefaultEnv() + $processInput->getEnv(),
$processInput->getInput(),
10,
);
}

/**
* @return array<string, false>
*/
private function getDefaultEnv(): array
{
$env = array_map(fn () => false, $_ENV);
$env + array_map(fn () => false, $_SERVER);

return $env;
}
}
14 changes: 14 additions & 0 deletions src/Process/ProcessFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace PhpSchool\PhpWorkshop\Process;

use PhpSchool\PhpWorkshop\Utils\ArrayObject;
use PhpSchool\PhpWorkshop\Utils\Collection;
use Symfony\Component\Process\Process;

interface ProcessFactory
{
public function create(ProcessInput $processInput): Process;
}
50 changes: 50 additions & 0 deletions src/Process/ProcessInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace PhpSchool\PhpWorkshop\Process;

class ProcessInput
{
/**
* @param list<string> $args
* @param array<string, string> $env
*/
public function __construct(
private string $executable,
private array $args,
private string $workingDirectory,
private array $env,
private ?string $input = null
) {
}

public function getExecutable(): string
{
return $this->executable;
}

/**
* @return list<string>
*/
public function getArgs(): array
{
return $this->args;
}

public function getWorkingDirectory(): string
{
return $this->workingDirectory;
}

/**
* @return array<string, string>
*/
public function getEnv(): array
{
return $this->env;
}

public function getInput(): ?string
{
return $this->input;
}
}
11 changes: 11 additions & 0 deletions src/Process/ProcessNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PhpSchool\PhpWorkshop\Process;

class ProcessNotFoundException extends \RuntimeException
{
public static function fromExecutable(string $executable): self
{
return new self(sprintf('Could not find executable: "%s"', $executable));
}
}
125 changes: 125 additions & 0 deletions test/Process/DockerProcessFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace PhpSchool\PhpWorkshopTest\Process;

use PhpSchool\PhpWorkshop\Process\DockerProcessFactory;
use PhpSchool\PhpWorkshop\Process\ProcessInput;
use PhpSchool\PhpWorkshop\Process\ProcessNotFoundException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\ExecutableFinder;

class DockerProcessFactoryTest extends TestCase
{
public function testCreateThrowsExceptionIfDockerNotFound(): void
{
static::expectException(ProcessNotFoundException::class);

$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn(null);

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('composer', [], __DIR__, []);

$factory->create($input);
}

public function testCreate(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('php', [], __DIR__, []);

$process = $factory->create($input);
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' 'runtime' 'php'", $process->getCommandLine());
static::assertSame('/docker-dir', $process->getWorkingDirectory());
}

public function testCreateMountsComposerCacheDirIfExecutableIsComposer(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('composer', [], __DIR__, []);

$process = $factory->create($input);
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());
static::assertSame('/docker-dir', $process->getWorkingDirectory());
}

public function testCreateWithArgs(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('php', ['one', 'two'], __DIR__, []);

$process = $factory->create($input);
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());
static::assertSame('/docker-dir', $process->getWorkingDirectory());
}

public function testCreateWithEnv(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('php', ['one', 'two'], __DIR__, ['SOME_VAR' => 'value']);

$process = $factory->create($input);
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());
static::assertSame('/docker-dir', $process->getWorkingDirectory());
}

public function testWithInput(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/composer-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('php', [], __DIR__, [], 'someinput');

$process = $factory->create($input);
static::assertSame("'/usr/local/bin/docker' 'compose' '-p' 'php8appreciate' '-f' '.docker/runtime/docker-compose.yml' 'run' '--rm' '-w' '/solution' 'runtime' 'php'", $process->getCommandLine());
static::assertSame('someinput', $process->getInput());
}

public function testSolutionDirectoryIsPassedAsEnvVar(): void
{
$finder = $this->createMock(ExecutableFinder::class);
$finder->expects($this->once())
->method('find')
->with('docker')
->willReturn('/usr/local/bin/docker');

$factory = new DockerProcessFactory('/docker-dir', 'php8appreciate', '/composer/cache/dir', $finder);
$input = new ProcessInput('php', ['one', 'two'], __DIR__, ['SOME_VAR' => 'value']);

$process = $factory->create($input);
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());
static::assertSame('/docker-dir', $process->getWorkingDirectory());
static::assertSame(['SOLUTION' => __DIR__], $process->getEnv());
}
}
Loading

0 comments on commit ac6ac0e

Please sign in to comment.