Skip to content

Commit c496fec

Browse files
authored
Merge pull request #213 from php-school/debug-logger
Debug logger
2 parents bf09946 + 4610ac3 commit c496fec

15 files changed

+231
-21
lines changed

app/config.php

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Colors\Color;
66
use PhpSchool\PhpWorkshop\Listener\InitialCodeListener;
7+
use PhpSchool\PhpWorkshop\Logger\ConsoleLogger;
78
use PhpSchool\PhpWorkshop\Logger\Logger;
89
use Psr\Log\LoggerInterface;
910
use function DI\create;
@@ -96,6 +97,10 @@
9697
$appName = $c->get('appName');
9798
$globalDir = $c->get('phpschoolGlobalDir');
9899

100+
if ($c->get('debugMode')) {
101+
return new ConsoleLogger($c->get(OutputInterface::class), $c->get(Color::class));
102+
}
103+
99104
return new Logger("$globalDir/logs/$appName.log");
100105
},
101106
ExerciseDispatcher::class => function (ContainerInterface $c) {

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"php-school/keylighter": "^0.8.4",
3131
"nikic/php-parser": "^4.0",
3232
"guzzlehttp/guzzle": "^7.2",
33-
"psr/log": "^1.1"
33+
"psr/log": "^1.1",
34+
"ext-json": "*"
3435
},
3536
"require-dev": {
3637
"composer/composer": "^2.0",

src/Application.php

+17-5
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ public function setBgColour(string $colour): void
169169
$this->bgColour = $colour;
170170
}
171171

172-
public function configure(): ContainerInterface
172+
public function configure(bool $debugMode = false): ContainerInterface
173173
{
174174
if ($this->container instanceof ContainerInterface) {
175175
return $this->container;
176176
}
177177

178-
$container = $this->getContainer();
178+
$container = $this->getContainer($debugMode);
179179

180180
foreach ($this->exercises as $exercise) {
181181
if (false === $container->has($exercise)) {
@@ -221,10 +221,20 @@ public function configure(): ContainerInterface
221221
*/
222222
public function run(): int
223223
{
224-
$container = $this->configure();
224+
$args = $_SERVER['argv'] ?? [];
225+
226+
$debug = any($args, function (string $arg) {
227+
return $arg === '--debug';
228+
});
229+
230+
$args = array_values(array_filter($args, function (string $arg) {
231+
return $arg !== '--debug';
232+
}));
233+
234+
$container = $this->configure($debug);
225235

226236
try {
227-
$exitCode = $container->get(CommandRouter::class)->route();
237+
$exitCode = $container->get(CommandRouter::class)->route($args);
228238
} catch (MissingArgumentException $e) {
229239
$container
230240
->get(OutputInterface::class)
@@ -261,9 +271,10 @@ public function run(): int
261271
}
262272

263273
/**
274+
* @param bool $debugMode
264275
* @return Container
265276
*/
266-
private function getContainer(): Container
277+
private function getContainer(bool $debugMode): Container
267278
{
268279
$containerBuilder = new ContainerBuilder();
269280
$containerBuilder->addDefinitions(
@@ -276,6 +287,7 @@ private function getContainer(): Container
276287
$containerBuilder->addDefinitions(
277288
[
278289
'workshopTitle' => $this->workshopTitle,
290+
'debugMode' => $debugMode,
279291
'exercises' => $this->exercises,
280292
'workshopLogo' => $this->logo,
281293
'bgColour' => $this->bgColour,

src/CommandRouter.php

-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ public function addCommand(CommandDefinition $c): void
100100
*/
101101
public function route(array $args = null): int
102102
{
103-
104103
if (null === $args) {
105104
$args = $_SERVER['argv'] ?? [];
106105
}

src/ExerciseRenderer.php

-2
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ public function __construct(
8282
*/
8383
public function __invoke(CliMenu $menu): void
8484
{
85-
$menu->close();
86-
8785
$item = $menu->getSelectedItem();
8886
$exercise = $this->exerciseRepository->findByName($item->getText());
8987
$exercises = $this->exerciseRepository->findAll();

src/Factory/MenuFactory.php

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public function __invoke(ContainerInterface $c): CliMenu
5959
$builder->addItem(
6060
$exercise->getName(),
6161
function (CliMenu $menu) use ($exerciseRenderer, $eventDispatcher, $exercise) {
62+
$menu->close();
6263
$this->dispatchExerciseSelectedEvent($eventDispatcher, $exercise);
6364
$exerciseRenderer->__invoke($menu);
6465
},

src/Logger/ConsoleLogger.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\PhpWorkshop\Logger;
6+
7+
use Colors\Color;
8+
use PhpSchool\PhpWorkshop\Output\OutputInterface;
9+
use Psr\Log\AbstractLogger;
10+
use Psr\Log\LoggerInterface;
11+
12+
class ConsoleLogger extends AbstractLogger implements LoggerInterface
13+
{
14+
/**
15+
* @var OutputInterface
16+
*/
17+
private $output;
18+
19+
/**
20+
* @var Color
21+
*/
22+
private $color;
23+
24+
public function __construct(OutputInterface $output, Color $color)
25+
{
26+
$this->output = $output;
27+
$this->color = $color;
28+
}
29+
30+
public function log($level, $message, array $context = []): void
31+
{
32+
$parts = [
33+
sprintf(
34+
'%s - %s - %s',
35+
$this->color->fg('yellow', (new \DateTime())->format('H:i:s')),
36+
$this->color->bg('red', strtoupper($level)),
37+
$this->color->fg('red', $message)
38+
),
39+
json_encode($context, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
40+
];
41+
42+
$this->output->writeLine(implode("\n", $parts));
43+
}
44+
}

src/functions.php

+16
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,19 @@ function collect(array $array): Collection
7373
return new Collection($array);
7474
}
7575
}
76+
77+
78+
if (!function_exists('any')) {
79+
80+
/**
81+
* @param array<mixed> $values
82+
* @param callable $cb
83+
* @return bool
84+
*/
85+
function any(array $values, callable $cb): bool
86+
{
87+
return array_reduce($values, function (bool $carry, $value) use ($cb) {
88+
return $carry || $cb($value);
89+
}, false);
90+
}
91+
}

test/ApplicationTest.php

+14-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
use PhpSchool\PhpWorkshopTest\Asset\MockEventDispatcher;
1616
use Psr\Log\LoggerInterface;
1717
use Psr\Log\NullLogger;
18+
use PhpSchool\PhpWorkshop\Logger\ConsoleLogger;
19+
use PhpSchool\PhpWorkshop\Logger\Logger;
20+
use PHPUnit\Framework\TestCase;
1821

1922
class ApplicationTest extends BaseTest
2023
{
@@ -53,7 +56,7 @@ public function testEventListenersFromLocalAndWorkshopConfigAreMerged(): void
5356
$rp->setAccessible(true);
5457
$rp->setValue($app, $frameworkFile);
5558

56-
$container = $rm->invoke($app);
59+
$container = $rm->invoke($app, false);
5760

5861
$eventListeners = $container->get('eventListeners');
5962

@@ -162,8 +165,16 @@ public function testConfigureReturnsSameContainerInstance(): void
162165
self::assertSame($application->configure(), $application->configure());
163166
}
164167

165-
public function tearDown(): void
168+
public function testDebugFlagSwitchesLoggerToConsoleLogger(): void
166169
{
167-
parent::tearDown();
170+
$configFile = $this->getTemporaryFile('config.php', '<?php return [];');
171+
$application = new Application('My workshop', $configFile);
172+
$container = $application->configure(true);
173+
174+
$container->set('phpschoolGlobalDir', $this->getTemporaryDirectory());
175+
$container->set('appName', 'my-workshop');
176+
177+
$logger = $container->get(LoggerInterface::class);
178+
self::assertInstanceOf(ConsoleLogger::class, $logger);
168179
}
169180
}

test/BaseTest.php

+2-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ abstract class BaseTest extends TestCase
1616
public function getTemporaryDirectory(): string
1717
{
1818
if (!$this->tempDirectory) {
19-
$tempDirectory = System::tempDir($this->getName());
20-
mkdir($tempDirectory, 0777, true);
21-
22-
$this->tempDirectory = realpath($tempDirectory);
19+
$this->tempDirectory = System::tempDir($this->getName());
20+
mkdir($this->tempDirectory, 0777, true);
2321
}
2422

2523
return $this->tempDirectory;

test/ExerciseRendererTest.php

-4
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ public function testExerciseRendererSetsCurrentExerciseAndRendersExercise(): voi
3333
->method('getSelectedItem')
3434
->willReturn($item);
3535

36-
$menu
37-
->expects($this->once())
38-
->method('close');
39-
4036
$exercise1 = $this->createMock(ExerciseInterface::class);
4137
$exercise2 = $this->createMock(ExerciseInterface::class);
4238
$exercises = [$exercise1, $exercise2];

test/Factory/MenuFactoryTest.php

+81-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PhpSchool\PhpWorkshopTest\Factory;
44

5+
use PhpSchool\CliMenu\MenuItem\SelectableItem;
6+
use PhpSchool\PhpWorkshop\Event\EventInterface;
57
use Psr\Container\ContainerInterface;
68
use PhpSchool\CliMenu\CliMenu;
79
use PhpSchool\PhpWorkshop\Command\CreditsCommand;
@@ -68,6 +70,84 @@ public function testFactoryReturnsInstance(): void
6870

6971

7072
$factory = new MenuFactory();
71-
$this->assertInstanceOf(CliMenu::class, $factory($container));
73+
74+
$factory($container);
75+
}
76+
77+
public function testSelectExercise(): void
78+
{
79+
$container = $this->createMock(ContainerInterface::class);
80+
$userStateSerializer = $this->createMock(UserStateSerializer::class);
81+
$userStateSerializer
82+
->expects($this->once())
83+
->method('deSerialize')
84+
->willReturn(new UserState());
85+
86+
$exerciseRepository = $this->createMock(ExerciseRepository::class);
87+
$exercise = $this->createMock(ExerciseInterface::class);
88+
$exercise
89+
->method('getName')
90+
->willReturn('Exercise');
91+
$exerciseRepository
92+
->expects($this->once())
93+
->method('findAll')
94+
->willReturn([$exercise]);
95+
96+
$terminal = $this->createMock(Terminal::class);
97+
$terminal
98+
->method('getWidth')
99+
->willReturn(70);
100+
101+
$eventDispatcher = $this->createMock(EventDispatcher::class);
102+
$eventDispatcher
103+
->expects(self::exactly(2))
104+
->method('dispatch')
105+
->withConsecutive(
106+
[
107+
self::callback(function ($event) {
108+
return $event instanceof EventInterface && $event->getName() === 'exercise.selected';
109+
})
110+
],
111+
[
112+
self::callback(function ($event) {
113+
return $event instanceof EventInterface && $event->getName() === 'exercise.selected.exercise';
114+
})
115+
]
116+
);
117+
118+
$exerciseRenderer = $this->createMock(ExerciseRenderer::class);
119+
$exerciseRenderer->expects(self::once())
120+
->method('__invoke')
121+
->with(self::isInstanceOf(CliMenu::class));
122+
123+
$services = [
124+
UserStateSerializer::class => $userStateSerializer,
125+
ExerciseRepository::class => $exerciseRepository,
126+
ExerciseRenderer::class => $exerciseRenderer,
127+
HelpCommand::class => $this->createMock(HelpCommand::class),
128+
CreditsCommand::class => $this->createMock(CreditsCommand::class),
129+
ResetProgress::class => $this->createMock(ResetProgress::class),
130+
'workshopLogo' => 'LOGO',
131+
'bgColour' => 'black',
132+
'fgColour' => 'green',
133+
'workshopTitle' => 'TITLE',
134+
WorkshopType::class => WorkshopType::STANDARD(),
135+
EventDispatcher::class => $eventDispatcher,
136+
Terminal::class => $terminal
137+
];
138+
139+
$container
140+
->method('get')
141+
->willReturnCallback(function ($name) use ($services) {
142+
return $services[$name];
143+
});
144+
145+
146+
$factory = new MenuFactory();
147+
148+
$menu = $factory($container);
149+
150+
$firstExercise = $menu->getItemByIndex(6);
151+
$menu->executeAsSelected($firstExercise);
72152
}
73153
}

test/FunctionsTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,15 @@ public function camelCaseToKebabCaseProvider(): array
4040
]
4141
];
4242
}
43+
44+
public function testAny(): void
45+
{
46+
self::assertEquals(true, any([1, 2, 3, 10, 11], function (int $num) {
47+
return $num > 10;
48+
}));
49+
50+
self::assertEquals(false, any([1, 2, 3, 10, 11], function (int $num) {
51+
return $num > 11;
52+
}));
53+
}
4354
}

test/Logger/ConsoleLoggerTest.php

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace PhpSchool\PhpWorkshopTest\Logger;
4+
5+
use PhpSchool\CliMenu\Util\StringUtil;
6+
use PhpSchool\PhpWorkshop\Logger\ConsoleLogger;
7+
use PhpSchool\PhpWorkshop\Utils\StringUtils;
8+
use PhpSchool\PhpWorkshopTest\ContainerAwareTest;
9+
use Psr\Log\LoggerInterface;
10+
11+
class ConsoleLoggerTest extends ContainerAwareTest
12+
{
13+
public function setUp(): void
14+
{
15+
parent::setUp();
16+
17+
$this->container->set('phpschoolGlobalDir', $this->getTemporaryDirectory());
18+
$this->container->set('appName', 'my-workshop');
19+
$this->container->set('debugMode', true);
20+
}
21+
22+
public function testConsoleLoggerIsCreatedIfDebugModeEnable(): void
23+
{
24+
$this->assertInstanceOf(ConsoleLogger::class, $this->container->get(LoggerInterface::class));
25+
}
26+
27+
public function testLoggerWithContext(): void
28+
{
29+
$logger = $this->container->get(LoggerInterface::class);
30+
$logger->critical('Failed to copy file', ['exercise' => 'my-exercise']);
31+
32+
$out = StringUtil::stripAnsiEscapeSequence($this->getActualOutputForAssertion());
33+
34+
$match = '/\d{2}\:\d{2}\:\d{2} - CRITICAL - Failed to copy file\n{\n "exercise": "my-exercise"\n}/';
35+
$this->assertMatchesRegularExpression($match, $out);
36+
}
37+
}

test/Logger/LoggerTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function setUp(): void
1515

1616
$this->container->set('phpschoolGlobalDir', $this->getTemporaryDirectory());
1717
$this->container->set('appName', 'my-workshop');
18+
$this->container->set('debugMode', false);
1819
}
1920

2021
public function testLoggerDoesNotCreateFileIfNoMessageIsLogged(): void

0 commit comments

Comments
 (0)