GenerateFunction   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 45
c 1
b 0
f 0
dl 0
loc 149
ccs 38
cts 38
cp 1
rs 10
wmc 14

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A generateEndpoint() 0 9 1
A generateRoute() 0 23 6
A generateDefault() 0 11 2
A genArg() 0 11 3
A __invoke() 0 27 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jasny\SwitchRoute\Generator;
6
7
use Jasny\SwitchRoute\Endpoint;
8
use Jasny\SwitchRoute\InvalidRouteException;
9
use Jasny\SwitchRoute\Invoker;
10
use Jasny\SwitchRoute\InvokerInterface;
11
use ReflectionException;
12
13
/**
14
 * Generate a function that invokes an action based on the route.
15
 */
16
class GenerateFunction extends AbstractGenerate
17
{
18
    /**
19
     * @var InvokerInterface
20
     */
21
    protected $invoker;
22
23
    /**
24
     * GenerateScript constructor.
25
     *
26
     * @param InvokerInterface $invoker
27
     */
28 8
    public function __construct(InvokerInterface $invoker = null)
29
    {
30 8
        $this->invoker = $invoker ?? new Invoker();
31
    }
32
33
    /**
34
     * Invoke code generation.
35
     *
36
     * @param string $name       Function name
37
     * @param array  $routes     Ignored
38
     * @param array  $structure
39
     * @return string
40
     */
41 6
    public function __invoke(string $name, array $routes, array $structure): string
42
    {
43 6
        $default = $structure["\e"] ?? null;
44 6
        unset($structure["\e"]);
45
46 6
        $switchCode = self::indent($this->generateSwitch($structure));
47 4
        $defaultCode = self::indent($this->generateDefault($default));
48
49 4
        [$namespace, $function] = $this->generateNs($name);
50
51
        return <<<CODE
52
<?php
53
54
declare(strict_types=1);
55
{$namespace}
56
/**
57
 * This function is generated by SwitchRoute.
58
 * Do not modify it manually. Any changes will be overwritten.
59
 */
60
function {$function}(string \$method, string \$path, ?callable \$instantiate = null)
61
{
62
    \$segments = \$path === "/" ? [] : explode("/", trim(\$path, "/"));
63
    \$allowedMethods = [];
64
65
$switchCode
66
67
$defaultCode
68
}
69
CODE;
70
    }
71
72
    /**
73
     * Generate code for an endpoint
74
     *
75
     * @param Endpoint $endpoint
76
     * @return string
77
     */
78 7
    protected function generateEndpoint(Endpoint $endpoint): string
79
    {
80 7
        $exportValue = function ($var): string {
81 6
            return var_export($var, true);
82
        };
83
84 7
        return join("\n", [
85 7
            "\$allowedMethods = [" . join(', ', array_map($exportValue, $endpoint->getAllowedMethods())) . "];",
86 7
            parent::generateEndpoint($endpoint)
87
        ]);
88
    }
89
90
    /**
91
     * Generate routing code for an endpoint.
92
     *
93
     * @param string        $key
94
     * @param array         $route
95
     * @param array         $vars
96
     * @param callable|null $genArg
97
     * @return string
98
     * @throws InvalidRouteException
99
     */
100 7
    protected function generateRoute(string $key, array $route, array $vars, ?callable $genArg = null): string
101
    {
102 7
        if (!isset($route['include']) && !isset($route['controller']) && !isset($route['action'])) {
103 1
            throw new InvalidRouteException("Route for '$key' should specify 'include', 'controller', " .
104
                "or 'action'");
105
        }
106
107 6
        if (isset($route['include'])) {
108 1
            return "return require '" . addslashes($route['include']) . "';";
109
        }
110
111
        try {
112 6
            $genArg = $genArg ?? function (?string $name, ?string $type = null, $default = null) use ($vars): string {
113 2
                return $this->genArg($vars, $name, $type, $default);
114
            };
115 6
            $new = '(isset($instantiate) ? ($instantiate)(\'%1$s\') : new \\%1$s)';
116
117 6
            $invocation = $this->invoker->generateInvocation($route, $genArg, $new);
118 2
        } catch (ReflectionException $exception) {
119 2
            throw new InvalidRouteException("Invalid route for '$key'. ". $exception->getMessage(), 0, $exception);
120
        }
121
122 4
        return "return $invocation;";
123
    }
124
125
    /**
126
     * Generate code for when no route matches.
127
     *
128
     * @param Endpoint|null $endpoint
129
     * @return string
130
     * @throws InvalidRouteException
131
     */
132 5
    protected function generateDefault(?Endpoint $endpoint): string
133
    {
134 5
        if ($endpoint === null) {
135 4
            return $this->invoker->generateDefault();
136
        }
137
138 1
        $genArg = function ($name, $type = null, $default = null) {
139 1
            return "\${$name} ?? " . var_export($default, true);
140
        };
141
142 1
        return $this->generateRoute('default', $endpoint->getRoutes()[''], [], $genArg);
143
    }
144
145
    /**
146
     * Generate code for argument using path vars.
147
     *
148
     * @param array       $vars
149
     * @param string|null $name
150
     * @param string|null $type
151
     * @param mixed       $default
152
     * @return string
153
     */
154 3
    protected function genArg(array $vars, ?string $name, ?string $type = null, $default = null): string
155
    {
156 3
        if ($name === null) {
157 2
            $fnMap = function ($name, $pos) {
158 2
                return sprintf('"%s" => $segments[%d]', addslashes($name), $pos);
159
            };
160
161 2
            return '[' . join(', ', array_map($fnMap, array_keys($vars), $vars)) . ']';
162
        }
163
164 2
        return isset($vars[$name]) ? "\$segments[{$vars[$name]}]" : var_export($default, true);
165
    }
166
}
167