• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

strictlyPHP / dolphin / #9

18 May 2025 10:11PM UTC coverage: 88.889% (-11.1%) from 100.0%
#9

push

web-flow
add dependency injection and improve routing (#23)

* add dependency injection and improve routing

* add dto mapping and request injection

* add logging

* edit tests

* fixes

127 of 148 new or added lines in 4 files covered. (85.81%)

168 of 189 relevant lines covered (88.89%)

2.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.0
/src/App.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace StrictlyPHP\Dolphin;
6

7
use DI\ContainerBuilder;
8
use HaydenPierce\ClassFinder\ClassFinder;
9
use League\Route\Router;
10
use Monolog\Handler\StreamHandler;
11
use Monolog\Level;
12
use Monolog\Logger;
13
use Psr\Http\Server\RequestHandlerInterface;
14
use Psr\Log\LoggerInterface;
15
use ReflectionClass;
16
use Slim\Psr7\Factory\ResponseFactory;
17
use Slim\Psr7\Factory\StreamFactory;
18
use Slim\Psr7\Factory\UriFactory;
19
use Slim\Psr7\Headers;
20
use Slim\Psr7\Request;
21
use StrictlyPHP\Dolphin\Attributes\Route;
22
use StrictlyPHP\Dolphin\Request\Method;
23
use StrictlyPHP\Dolphin\Strategy\DolphinAppStrategy;
24
use StrictlyPHP\Dolphin\Strategy\DtoMapper;
25

26
class App
27
{
28
    public function __construct(
29
        private readonly RequestHandlerInterface $router,
30
        private readonly ?LoggerInterface $logger = null
31
    ) {
32
    }
6✔
33

34
    /**
35
     * @param string[] $controllers
36
     */
37
    public static function build(
38
        array $controllers,
39
    ): self {
40
        if (empty($controllers)) {
4✔
NEW
41
            throw new \InvalidArgumentException('No controllers provided');
×
42
        }
43
        $container = (new ContainerBuilder())
4✔
44
            ->useAttributes(true)
4✔
45
            ->build();
4✔
46

47
        $logger = new Logger('dolphin');
4✔
48
        // Log INFO and above to stdout
49
        $logger->pushHandler(new StreamHandler('php://stdout', Level::Info));
4✔
50

51
        // Log WARNING and above to stderr
52
        $logger->pushHandler(new StreamHandler('php://stderr', Level::Warning));
4✔
53

54
        $container->set(LoggerInterface::class, new Logger('dolphin_logger'));
4✔
55

56
        $strategy = new DolphinAppStrategy(
4✔
57
            new DtoMapper(),
4✔
58
            new ResponseFactory(),
4✔
59
            $logger
4✔
60
        );
4✔
61
        $strategy->setContainer($container);
4✔
62
        $router = new Router();
4✔
63
        $router->setStrategy($strategy);
4✔
64

65
        $classes = [];
4✔
66
        ClassFinder::disablePSR4Vendors();
4✔
67
        foreach ($controllers as $controller) {
4✔
68
            if (class_exists($controller)) {
4✔
69
                $classes[] = $controller;
4✔
70
            } else {
NEW
71
                $classes = array_merge(
×
NEW
72
                    $classes,
×
NEW
73
                    ClassFinder::getClassesInNamespace($controller, ClassFinder::RECURSIVE_MODE)
×
NEW
74
                );
×
75
            }
76
        }
77

78
        if (empty($classes)) {
4✔
NEW
79
            throw new \InvalidArgumentException('No classes found');
×
80
        }
81

82
        foreach ($classes as $class) {
4✔
83
            $reflection = new ReflectionClass($class);
4✔
84
            $attributes = $reflection->getAttributes(Route::class);
4✔
85
            foreach ($attributes as $attribute) {
4✔
86
                /** @var Method $requestMethod */
87
                $requestMethod = $attribute->getArguments()[0];
4✔
88

89
                /** @var string $requestPath */
90
                $requestPath = $attribute->getArguments()[1];
4✔
91

92
                $router->map($requestMethod->value, $requestPath, $class);
4✔
93
            }
94
        }
95

96
        return new self($router, $logger);
4✔
97
    }
98

99
    public function getRouter(): RequestHandlerInterface
100
    {
101
        return $this->router;
2✔
102
    }
103

104
    public function run(array $event, object $context): array
105
    {
106
        parse_str($event['http']['headers']['cookie'] ?? '', $cookies);
6✔
107
        $request = new Request(
6✔
108
            $event['http']['method'],
6✔
109
            (new UriFactory())->createUri(
6✔
110
                sprintf(
6✔
111
                    '%s/%s%s',
6✔
112
                    $context->apiHost,
6✔
113
                    $event['http']['path'],
6✔
114
                    ! empty($event['http']['queryString']) ? '?' . $event['http']['queryString'] : ''
6✔
115
                )
6✔
116
            ),
6✔
117
            new Headers($event['http']['headers']),
6✔
118
            $cookies,
6✔
119
            [],
6✔
120
            (new StreamFactory())->createStream($event['http']['body'])
6✔
121
        );
6✔
122

123
        try {
124
            $response = $this->router->handle($request);
6✔
125

126
            return [
5✔
127
                'statusCode' => $response->getStatusCode(),
5✔
128
                'body' => (string) $response->getBody(),
5✔
129
                'headers' => $response->getHeaders(),
5✔
130
            ];
5✔
131
        } catch (\Exception $e) {
1✔
132
            if ($this->logger) {
1✔
NEW
133
                $this->logger->error($e->getMessage(), [
×
NEW
134
                    'trace' => $e->getTrace(),
×
NEW
135
                ]);
×
136
            }
137
            return [
1✔
138
                'statusCode' => 500,
1✔
139
                'body' => json_encode([
1✔
140
                    'error' => $e->getMessage(),
1✔
141
                    'trace' => $e->getTrace(),
1✔
142
                ]),
1✔
143
                'headers' => [
1✔
144
                    'Content-Type' => 'application/json',
1✔
145
                ],
1✔
146
            ];
1✔
147
        }
148
    }
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc