Skip to content

Commit 95afade

Browse files
Feature/builder dependency (#16)
* Add CommandDispatcherInterface This interface beside CommandInterface is used to dewire the hard dependency on symfony processbuilder inside SystemCtl This way testing and mocking the desired result should be alot easier Resolve #15
1 parent fed6978 commit 95afade

17 files changed

+501
-328
lines changed

Vagrantfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Vagrant.configure("2") do |config|
44
config.vm.provision "shell", inline: <<-SHELL
55
add-apt-repository ppa:ondrej/php
66
apt-get update
7-
apt-get install -y php7.1 php7.1-xml php7.1-mbstring php7.1-zip composer
7+
apt-get install -y php7.1 php7.1-xml php7.1-mbstring php7.1-zip php7.1-curl php7.1-xdebug composer
8+
9+
# Switch to /vagrant and install packages
10+
cd /vagrant && composer install
811
SHELL
912
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace SystemCtl\Command;
4+
5+
use SystemCtl\Exception\CommandFailedException;
6+
7+
interface CommandDispatcherInterface
8+
{
9+
/**
10+
* Timeout after which the dispatcher failes the execution
11+
*
12+
* @param int $timeout
13+
*
14+
* @return CommandDispatcherInterface
15+
*/
16+
public function setTimeout(int $timeout): CommandDispatcherInterface;
17+
18+
/**
19+
* Set basic binary to dispatch
20+
*
21+
* @param string $binary
22+
*
23+
* @return CommandDispatcherInterface
24+
*/
25+
public function setBinary(string $binary): CommandDispatcherInterface;
26+
27+
/**
28+
* Dispatch given commands against implementers logic and creating a new command
29+
* to read results
30+
*
31+
* @param array $commands
32+
*
33+
* @return CommandInterface
34+
* @throws CommandFailedException
35+
*/
36+
public function dispatch(...$commands): CommandInterface;
37+
}

src/Command/CommandInterface.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
4+
namespace SystemCtl\Command;
5+
6+
use SystemCtl\Exception\CommandFailedException;
7+
8+
/**
9+
* Interface CommandInterface
10+
*
11+
* @package SystemCtl\Command
12+
* @author icanhazstring <[email protected]>
13+
*/
14+
interface CommandInterface
15+
{
16+
/**
17+
* @return CommandInterface
18+
* @throws CommandFailedException
19+
*/
20+
public function run(): CommandInterface;
21+
22+
/**
23+
* @return string
24+
*/
25+
public function getOutput(): string;
26+
27+
/**
28+
* @return bool
29+
*/
30+
public function isSuccessful(): bool;
31+
}

src/Command/SymfonyCommand.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace SystemCtl\Command;
4+
5+
use Symfony\Component\Process\Process;
6+
use SystemCtl\Exception\CommandFailedException;
7+
8+
/**
9+
* Class SymfonyCommand
10+
*
11+
* @package SystemCtl\Command
12+
* @author icanhazstring <[email protected]>
13+
*/
14+
class SymfonyCommand implements CommandInterface
15+
{
16+
/** @var Process */
17+
private $process;
18+
19+
public function __construct(Process $process)
20+
{
21+
$this->process = $process;
22+
}
23+
24+
/**
25+
* @inheritdoc
26+
*/
27+
public function getOutput(): string
28+
{
29+
return $this->process->getOutput();
30+
}
31+
32+
/**
33+
* @inheritdoc
34+
*/
35+
public function isSuccessful(): bool
36+
{
37+
return $this->process->isSuccessful();
38+
}
39+
40+
/**
41+
* @inheritdoc
42+
*/
43+
public function run(): CommandInterface
44+
{
45+
$this->process->run();
46+
47+
if (!$this->process->isSuccessful()) {
48+
throw new CommandFailedException($this->process->getErrorOutput());
49+
}
50+
51+
return $this;
52+
}
53+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace SystemCtl\Command;
4+
5+
use Symfony\Component\Process\ProcessBuilder;
6+
7+
/**
8+
* Class SymfonyCommandDispatcher
9+
*
10+
* @package SystemCtl\Command
11+
* @author icanhazstring <[email protected]>
12+
*/
13+
class SymfonyCommandDispatcher implements CommandDispatcherInterface
14+
{
15+
private $binary;
16+
private $timetout;
17+
18+
/**
19+
* @inheritdoc
20+
*/
21+
public function setBinary(string $binary): CommandDispatcherInterface
22+
{
23+
$this->binary = $binary;
24+
25+
return $this;
26+
}
27+
28+
/**
29+
* @inheritdoc
30+
*/
31+
public function setTimeout(int $timeout): CommandDispatcherInterface
32+
{
33+
$this->timetout = $timeout;
34+
35+
return $this;
36+
}
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
public function dispatch(...$commands): CommandInterface
42+
{
43+
$processBuilder = new ProcessBuilder();
44+
$processBuilder->setPrefix($this->binary);
45+
$processBuilder->setTimeout($this->timetout);
46+
$processBuilder->setArguments($commands);
47+
48+
$process = new SymfonyCommand($processBuilder->getProcess());
49+
50+
return $process->run();
51+
}
52+
}

src/Exception/CommandFailedException.php

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,4 @@
44

55
class CommandFailedException extends \Exception
66
{
7-
/**
8-
* @param $name
9-
* @param $command
10-
*
11-
* @return CommandFailedException
12-
*/
13-
public static function fromService($name, $command): CommandFailedException
14-
{
15-
return new self(sprintf('Failed to %s service %s', $command, $name));
16-
}
17-
18-
/**
19-
* @param $name
20-
* @param $command
21-
*
22-
* @return CommandFailedException
23-
*/
24-
public static function fromTimer($name, $command): CommandFailedException
25-
{
26-
return new self(sprintf('Failed to %s timer %s', $command, $name));
27-
}
287
}

src/SystemCtl.php

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
namespace SystemCtl;
44

5-
use Symfony\Component\Process\ProcessBuilder;
5+
use SystemCtl\Command\CommandDispatcherInterface;
6+
use SystemCtl\Command\SymfonyCommandDispatcher;
67
use SystemCtl\Exception\UnitTypeNotSupportedException;
78
use SystemCtl\Unit\Service;
89
use SystemCtl\Unit\Timer;
@@ -35,6 +36,8 @@ class SystemCtl
3536
Timer::UNIT,
3637
];
3738

39+
private $commandDispatcher;
40+
3841
/**
3942
* Change systemctl binary
4043
*
@@ -61,6 +64,7 @@ public static function setTimeout(int $timeout): void
6164
*
6265
* @return UnitInterface
6366
* @throws UnitTypeNotSupportedException
67+
* @deprecated This static method is deprecated, please refer to a specifc get method for a unit
6468
*/
6569
public static function unitFromSuffix(string $unitSuffix, string $unitName): UnitInterface
6670
{
@@ -70,105 +74,121 @@ public static function unitFromSuffix(string $unitSuffix, string $unitName): Uni
7074
throw new UnitTypeNotSupportedException('Unit type ' . $unitSuffix . ' not supported');
7175
}
7276

73-
return new $unitClass($unitName, new ProcessBuilder([self::$binary]));
77+
$commandDispatcher = (new SymfonyCommandDispatcher)
78+
->setTimeout(self::$timeout)
79+
->setBinary(self::$binary);
80+
81+
return new $unitClass($unitName, $commandDispatcher);
7482
}
7583

7684
/**
7785
* List all supported units
7886
*
7987
* @param null|string $unitPrefix
80-
* @param string[] $unitTypes
88+
* @param string[] $unitTypes
89+
*
8190
* @return array|\string[]
8291
*/
8392
public function listUnits(?string $unitPrefix = null, array $unitTypes = self::SUPPORTED_UNITS): array
8493
{
85-
$processBuilder = $this->getProcessBuilder()
86-
->add('list-units');
94+
$commands = ['list-units'];
8795

8896
if ($unitPrefix) {
89-
$processBuilder->add($unitPrefix . '*');
97+
$commands[] = [$unitPrefix . '*'];
9098
}
9199

92-
$process = $processBuilder->getProcess();
93-
94-
$process->run();
95-
$output = $process->getOutput();
100+
$output = $this->getCommandDispatcher()->dispatch(...$commands)->getOutput();
96101

97102
return array_reduce($unitTypes, function ($carry, $unitSuffix) use ($output) {
98103
$result = Utils\OutputFetcher::fetchUnitNames($unitSuffix, $output);
104+
99105
return array_merge($carry, $result);
100106
}, []);
101107
}
102108

103109
/**
104110
* @param string $name
111+
*
105112
* @return Service
106113
*/
107114
public function getService(string $name): Service
108115
{
109-
return new Service($name, $this->getProcessBuilder());
116+
return new Service($name, $this->getCommandDispatcher());
110117
}
111118

112119
/**
113120
* @param null|string $unitPrefix
121+
*
114122
* @return Service[]
115123
*/
116124
public function getServices(?string $unitPrefix = null): array
117125
{
118126
$units = $this->listUnits($unitPrefix, [Service::UNIT]);
119127

120128
return array_map(function ($unitName) {
121-
return new Service($unitName, $this->getProcessBuilder());
129+
return new Service($unitName, $this->getCommandDispatcher());
122130
}, $units);
123131
}
124132

125133
/**
126134
* @param string $name
135+
*
127136
* @return Timer
128137
*/
129138
public function getTimer(string $name): Timer
130139
{
131-
return new Timer($name, $this->getProcessBuilder());
140+
return new Timer($name, $this->getCommandDispatcher());
132141
}
133142

134143
/**
135144
* @param null|string $unitPrefix
145+
*
136146
* @return Timer[]
137147
*/
138148
public function getTimers(?string $unitPrefix = null): array
139149
{
140150
$units = $this->listUnits($unitPrefix, [Timer::UNIT]);
141151

142152
return array_map(function ($unitName) {
143-
return new Timer($unitName, $this->getProcessBuilder());
153+
return new Timer($unitName, $this->getCommandDispatcher());
144154
}, $units);
145155
}
146156

147157
/**
148-
* @return ProcessBuilder
158+
* Restart the daemon to reload specs and new units
159+
*
160+
* @return bool
149161
*/
150-
public function getProcessBuilder(): ProcessBuilder
162+
public function daemonReload(): bool
151163
{
152-
$builder = ProcessBuilder::create();
153-
$builder->setPrefix(self::$binary);
154-
$builder->setTimeout(self::$timeout);
164+
return $this->getCommandDispatcher()->dispatch('daemon-reload')->isSuccessful();
165+
}
155166

156-
return $builder;
167+
/**
168+
* @return CommandDispatcherInterface
169+
*/
170+
public function getCommandDispatcher(): CommandDispatcherInterface
171+
{
172+
if ($this->commandDispatcher === null) {
173+
$this->commandDispatcher = (new SymfonyCommandDispatcher)
174+
->setTimeout(self::$timeout)
175+
->setBinary(self::$binary);
176+
}
177+
178+
return $this->commandDispatcher;
157179
}
158180

159181
/**
160-
* Restart the daemon to reload specs and new units
182+
* @param CommandDispatcherInterface $dispatcher
161183
*
162-
* @return bool
184+
* @return SystemCtl
163185
*/
164-
public function daemonReload(): bool
186+
public function setCommandDispatcher(CommandDispatcherInterface $dispatcher)
165187
{
166-
$processBuilder = $this->getProcessBuilder();
167-
$processBuilder->add('daemon-reload');
168-
169-
$process = $processBuilder->getProcess();
170-
$process->run();
188+
$this->commandDispatcher = $dispatcher
189+
->setTimeout(self::$timeout)
190+
->setBinary(self::$binary);
171191

172-
return $process->isSuccessful();
192+
return $this;
173193
}
174194
}

0 commit comments

Comments
 (0)