Skip to content

Commit 3a6f010

Browse files
committed
added onSignal
1 parent ce15380 commit 3a6f010

File tree

8 files changed

+242
-12
lines changed

8 files changed

+242
-12
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
All notable changes to `Laravel Console Facade` will be documented in this file
44

5+
## 1.7.0 - 2022-08-30
6+
- added `onSignal`, to allow more control over what happens on given signals.
7+
58
## 1.6.0 - 2022-08-19
69
- added components method to the new Components Factory of Laravel
710

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class MyClass {
6363
{
6464
Console::section('section1')->table(['header'=>'title'], [[]]);
6565
Console::section('section2')->withProgressBar(100, fn()=>true);
66+
Console::section('section1')->components()->bulletList(['this one', 'Another one']);
6667
Console::section('section1')->clear();
6768
Console::section('section3')->info('This message was brought to you by Henzeb');
6869
}
@@ -133,6 +134,43 @@ Console::onExit(
133134
);
134135
```
135136

137+
#### onSignal
138+
`onSignal` uses pcntl_signal to register the callbacks. But what's different, `onsignal` will allow you to register
139+
multiple handlers. This gives you more granular control over what is executed on certain signals without overriding
140+
existing handlers.
141+
142+
In below scenario, all three will run when a `SIGINT` signal is given and the second will also run when a `SIGTERM` signal
143+
is given. The first handler returns true. This means that when all handlers are executed, an exit is given.
144+
```php
145+
Console::onSignal(
146+
function () {
147+
print('first handler');
148+
return true;
149+
},
150+
SIGINT
151+
);
152+
153+
Console::onSignal(
154+
function () {
155+
print('second handler');
156+
var_dump(func_get_args());
157+
return false;
158+
},
159+
SIGINT,
160+
SIGTERM
161+
);
162+
163+
Console::onSignal(
164+
function () {
165+
print('third handler');
166+
},
167+
SIGINT
168+
);
169+
```
170+
171+
Tip: When a handler was already registered the normal way, you can use `pcntl_signal_get_handler` to pass this in to
172+
`onSignal`
173+
136174
## Testing
137175

138176
```bash

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"console",
88
"facade"
99
],
10-
"homepage": "https://github.com/henzeb/laravel-console",
10+
"homepage": "https://github.com/henzeb/laravel-console-facade",
1111
"license": "AGPL-3.0-only",
1212
"type": "library",
1313
"authors": [

src/Concerns/InteractsWithIO.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Henzeb\Console\Concerns;
4+
5+
use RuntimeException;
6+
use Illuminate\Console\View\Components\Factory;
7+
use Illuminate\Console\Concerns\InteractsWithIO as IlluminateInteractsWithIO;
8+
9+
trait InteractsWithIO
10+
{
11+
use IlluminateInteractsWithIO;
12+
13+
public function components(): Factory
14+
{
15+
if(version_compare(app()->version(), '9.21.0', '>=')) {
16+
return resolve(Factory::class, ['output' => $this->getOutput()]);
17+
}
18+
19+
throw new RuntimeException('This version of Laravel does not support components.');
20+
}
21+
}

src/Output/ConsoleOutput.php

+22-10
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
use Closure;
66
use Illuminate\Console\OutputStyle;
7+
use Henzeb\Console\Concerns\InteractsWithIO;
78
use Symfony\Component\Console\Input\ArrayInput;
8-
use Illuminate\Console\View\Components\Factory;
9-
use Illuminate\Console\Concerns\InteractsWithIO;
109
use Symfony\Component\Console\Style\SymfonyStyle;
1110
use Symfony\Component\Console\Input\InputInterface;
1211
use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput;
@@ -16,6 +15,7 @@ class ConsoleOutput
1615
use InteractsWithIO;
1716

1817
private array $sections = [];
18+
private array $onSignal = [];
1919
private array $onExit = [
2020
'always' => []
2121
];
@@ -79,8 +79,25 @@ private function getSection(string $name): ConsoleSectionOutput
7979

8080
public function onSignal(callable $onSignal, int ...$signalNumbers): void
8181
{
82-
foreach($signalNumbers as $signalNumber) {
83-
pcntl_signal($signalNumber, $onSignal);
82+
foreach ($signalNumbers as $signalNumber) {
83+
if (!isset($this->onSignal[$signalNumber])) {
84+
pcntl_signal(
85+
$signalNumber,
86+
function () use ($signalNumber) {
87+
$shouldExit = false;
88+
89+
foreach ($this->onSignal[$signalNumber] as $callable) {
90+
$shouldExit = $callable(...func_get_args()) ? true : $shouldExit;
91+
}
92+
93+
if ($shouldExit) {
94+
$this->exit();
95+
}
96+
}
97+
);
98+
}
99+
100+
$this->onSignal[$signalNumber][] = Closure::fromCallable($onSignal);
84101
}
85102
}
86103

@@ -91,7 +108,7 @@ public function onExit(callable $onExit, int $exitCode = null): void
91108

92109
private function getExitMethod(): callable
93110
{
94-
return $this->exitMethod = $this->exitMethod ?? fn(int $exitcode) => exit($exitcode);
111+
return $this->exitMethod = $this->exitMethod ?? fn(int $exitcode) => exit($exitcode);
95112
}
96113

97114
public function exit(int $exitcode = 0): void
@@ -106,9 +123,4 @@ public function exit(int $exitcode = 0): void
106123

107124
$this->getExitMethod()($exitcode);
108125
}
109-
110-
public function components(): Factory
111-
{
112-
return resolve(Factory::class, ['output' => $this->getOutput()]);
113-
}
114126
}

src/Output/ConsoleSectionOutput.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
use Closure;
66
use Generator;
7-
use Illuminate\Console\Concerns\InteractsWithIO;
7+
8+
use Henzeb\Console\Concerns\InteractsWithIO;
89
use Symfony\Component\Console\Helper\ProgressBar;
910
use Symfony\Component\Console\Input\InputInterface;
1011
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

tests/Unit/Console/Output/ConsoleOutputTest.php

+129
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,135 @@ public function testShouldCallWhenStatusCodeDoesMatch()
205205
$this->assertTrue($actual);
206206
}
207207

208+
public function testOnSignal()
209+
{
210+
$var = null;
211+
$output = new ConsoleOutput();
212+
213+
$output->onSignal(
214+
function () use (&$var) {
215+
$var = func_get_args();
216+
},
217+
SIGINT
218+
);
219+
pcntl_signal_get_handler(SIGINT)(
220+
0,
221+
[
222+
"signo" => 2,
223+
"errno" => 0,
224+
"code" => 0,
225+
]
226+
);
227+
228+
$this->assertEquals($var, [
229+
0,
230+
[
231+
"signo" => 2,
232+
"errno" => 0,
233+
"code" => 0,
234+
]
235+
]);
236+
}
237+
238+
public function testOnSignalMultipleSignals()
239+
{
240+
$var = 0;
241+
$output = new ConsoleOutput();
242+
$output->onSignal(
243+
function () use (&$var) {
244+
$var += 1;
245+
},
246+
SIGINT,
247+
SIGTERM
248+
);
249+
pcntl_signal_get_handler(SIGINT)();
250+
pcntl_signal_get_handler(SIGTERM)();
251+
252+
$this->assertEquals(2, $var);
253+
}
254+
255+
public function testOnSignalMultipleHandlers()
256+
{
257+
$var = 0;
258+
$output = new ConsoleOutput();
259+
260+
$this->mockExit($output, function () use (&$var) {
261+
$var += 4;
262+
});
263+
264+
$output->onSignal(
265+
function () use (&$var) {
266+
$var += 1;
267+
},
268+
SIGINT
269+
);
270+
271+
$output->onSignal(
272+
function () use (&$var) {
273+
$var += 2;
274+
},
275+
SIGINT
276+
);
277+
pcntl_signal_get_handler(SIGINT)();
278+
279+
$this->assertEquals(3, $var);
280+
}
281+
282+
public function testOnSignalMultipleHandlersExit()
283+
{
284+
$var = 0;
285+
$output = new ConsoleOutput();
286+
$this->mockExit($output, function () use (&$var) {
287+
$var += 4;
288+
});
289+
290+
$output->onSignal(
291+
function () use (&$var) {
292+
$var += 1;
293+
return true;
294+
},
295+
SIGINT
296+
);
297+
298+
$output->onSignal(
299+
function () use (&$var) {
300+
$var += 2;
301+
},
302+
SIGINT
303+
);
304+
pcntl_signal_get_handler(SIGINT)();
305+
306+
$this->assertEquals(7, $var);
307+
}
308+
309+
public function testOnSignalMultipleHandlersExitWithOneReturningFalse()
310+
{
311+
$var = 0;
312+
$output = new ConsoleOutput();
313+
$this->mockExit($output, function () use (&$var) {
314+
$var += 4;
315+
});
316+
317+
$output->onSignal(
318+
function () use (&$var) {
319+
$var += 1;
320+
return true;
321+
},
322+
SIGINT
323+
);
324+
325+
$output->onSignal(
326+
function () use (&$var) {
327+
$var += 2;
328+
return false;
329+
},
330+
SIGINT
331+
);
332+
pcntl_signal_get_handler(SIGINT)();
333+
334+
$this->assertEquals(7, $var);
335+
}
336+
208337
public function testShouldReturnComponentsFactory(): void
209338
{
210339
$output = new ConsoleOutput();

tests/Unit/Console/Output/ConsoleSectionOutputTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Console\Output\Output;
1010
use Henzeb\Console\Output\ConsoleSectionOutput;
1111
use Symfony\Component\Console\Input\ArrayInput;
12+
use Illuminate\Console\View\Components\Factory;
1213
use Symfony\Component\Console\Output\ConsoleOutput;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415
use Symfony\Component\Console\Formatter\OutputFormatter;
@@ -177,4 +178,29 @@ protected function providesVerbosityLevels(): array
177178
[OutputInterface::VERBOSITY_QUIET],
178179
];
179180
}
181+
182+
public function testShouldReturnComponentsFactory(): void
183+
{
184+
185+
$output = new \Henzeb\Console\Output\ConsoleOutput();
186+
187+
$expectedOutput = $output->getOutput();
188+
189+
$output = $output->section('test');
190+
191+
app()->bind(Factory::class, function ($app, $args) {
192+
return new class($args['output']) extends Factory {
193+
194+
};
195+
});
196+
$resolved = resolve(Factory::class, ['output' => $expectedOutput]);
197+
198+
$this->assertEquals(get_class($resolved), get_class($output->components()));
199+
200+
$actualOutput = Closure::bind(function () {
201+
return $this->output;
202+
}, $resolved, Factory::class)();
203+
204+
$this->assertTrue($expectedOutput === $actualOutput);
205+
}
180206
}

0 commit comments

Comments
 (0)