Completed
Pull Request — master (#90)
by
unknown
01:45 queued 43s
created

Compiler::compileEchoes()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.584
c 0
b 0
f 0
cc 3
nc 1
nop 1
1
<?php
2
3
namespace Spatie\BladeX;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Support\Collection;
7
8
class Compiler
9
{
10
    /** @var \Spatie\BladeX\BladeX */
11
    protected $bladeX;
12
13
    public function __construct(BladeX $bladeX)
14
    {
15
        return $this->bladeX = $bladeX;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
16
    }
17
18
    public function compile(string $viewContents): string
19
    {
20
        return array_reduce(
21
            $this->bladeX->registeredComponents(),
22
            [$this, 'parseComponentHtml'],
23
            $viewContents
24
        );
25
    }
26
27
    protected function parseComponentHtml(string $viewContents, Component $component)
28
    {
29
        $viewContents = $this->parseSlots($viewContents);
30
31
        $viewContents = $this->parseSelfClosingTags($viewContents, $component);
32
33
        $viewContents = $this->parseOpeningTags($viewContents, $component);
34
35
        $viewContents = $this->parseClosingTags($viewContents, $component);
36
37
        return $viewContents;
38
    }
39
40 View Code Duplication
    protected function parseSelfClosingTags(string $viewContents, Component $component): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
41
    {
42
        $pattern = "/<\s*{$component->getTag()}\s*(?<attributes>(?:\s+[\w\-:]+(=(?:\\\"[^\\\"]+\\\"|\'[^\']+\'|[^\'\\\"=<>]+))?)*\s*)\/>/";
43
44
        return preg_replace_callback($pattern, function (array $matches) use ($component) {
45
            $attributes = $this->getAttributesFromAttributeString($matches['attributes']);
46
47
            return $this->componentString($component, $attributes);
48
        }, $viewContents);
49
    }
50
51 View Code Duplication
    protected function parseOpeningTags(string $viewContents, Component $component): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
52
    {
53
        $pattern = "/<\s*{$component->getTag()}(?<attributes>(?:\s+[\w\-:]+(=(?:\\\"[^\\\"]*\\\"|\'[^\']*\'|[^\'\\\"=<>]+))?)*\s*)(?<![\/=\-])>/";
54
55
        return preg_replace_callback($pattern, function (array $matches) use ($component) {
56
            $attributes = $this->getAttributesFromAttributeString($matches['attributes']);
57
58
            return $this->componentStartString($component, $attributes);
59
        }, $viewContents);
60
    }
61
62
    protected function parseClosingTags(string $viewContents, Component $component): string
63
    {
64
        $pattern = "/<\/\s*{$component->getTag()}\s*>/";
65
66
        return preg_replace($pattern, $this->componentEndString($component), $viewContents);
67
    }
68
69
    protected function componentString(Component $component, array $attributes = []): string
70
    {
71
        return $this->componentStartString($component, $attributes).$this->componentEndString($component);
72
    }
73
74
    protected function componentStartString(Component $component, array $attributes = []): string
75
    {
76
        $attributesString = $this->attributesToString($attributes);
77
78
        $componentAttributeString = "[{$attributesString}]";
79
80
        if ($component->view === 'bladex::context') {
81
            return " @php(app(Spatie\BladeX\ContextStack::class)->push({$componentAttributeString})) ";
82
        }
83
84
        if ($component->viewModel) {
85
            $componentAttributeString = "
86
                array_merge(
87
                    app(Spatie\BladeX\ContextStack::class)->read(),
88
                    {$componentAttributeString},
89
                    app(
90
                        '{$component->viewModel}',
91
                        array_merge(
92
                            app(Spatie\BladeX\ContextStack::class)->read(),
93
                            {$componentAttributeString}
94
                        )
95
                    )->toArray()
96
                )";
97
        }
98
99
        return " @component(
100
           '{$component->view}',
101
           array_merge(app(Spatie\BladeX\ContextStack::class)->read(),
102
           {$componentAttributeString})
103
        ) ";
104
    }
105
106
    protected function componentEndString(Component $component): string
107
    {
108
        if ($component->view === 'bladex::context') {
109
            return "@php(app(Spatie\BladeX\ContextStack::class)->pop())";
110
        }
111
112
        return ' @endcomponent ';
113
    }
114
115
    protected function getAttributesFromAttributeString(string $attributeString): array
116
    {
117
        $attributeString = $this->parseBindAttributes($attributeString);
118
119
        $pattern = '/(?<attribute>[\w:-]+)(=(?<value>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))?/';
120
121
        if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) {
122
            return [];
123
        }
124
125
        return collect($matches)->mapWithKeys(function ($match) {
126
            $attribute = Str::camel($match['attribute']);
127
            $value = $match['value'] ?? null;
128
129
            if (is_null($value)) {
130
                $value = 'true';
131
                $attribute = Str::start($attribute, 'bind:');
132
            }
133
134
            $value = $this->stripQuotes($value);
135
136
            if ($this->containsEchoes($value)) {
137
                $attribute = Str::start($attribute, 'bind:');
138
                $value = $this->compileEchoes($value);
139
            }
140
141
            if (Str::startsWith($attribute, 'bind:')) {
142
                $attribute = Str::after($attribute, 'bind:');
143
            } else {
144
                $value = str_replace("'", "\\'", $value);
145
                $value = "'{$value}'";
146
            }
147
148
            return [$attribute => $value];
149
        })->toArray();
150
    }
151
152
    protected function parseSlots(string $viewContents): string
153
    {
154
        $openingPattern = '/<\s*slot\s+name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/';
155
        $viewContents = preg_replace_callback($openingPattern, function ($matches) {
156
            $name = $this->stripQuotes($matches['name']);
157
158
            return " @slot('{$name}') ";
159
        }, $viewContents);
160
161
        $closingPattern = '/<\/\s*slot[^>]*>/';
162
        $viewContents = preg_replace($closingPattern, ' @endslot', $viewContents);
163
164
        return $viewContents;
165
    }
166
167
    protected function parseBindAttributes(string $attributeString): string
168
    {
169
        return preg_replace("/\s*:([\w-]+)=/m", ' bind:$1=', $attributeString);
170
    }
171
172
    protected function attributesToString(array $attributes): string
173
    {
174
        return collect($attributes)
175
            ->map(function (string $value, string $attribute) {
176
                return "'{$attribute}' => {$value}";
177
            })
178
            ->implode(',');
179
    }
180
181
    protected function stripQuotes(string $string): string
182
    {
183
        if (Str::startsWith($string, ['"', '\''])) {
184
            return substr($string, 1, -1);
185
        }
186
187
        return $string;
188
    }
189
190
    protected function containsEchoes(string $value): bool
191
    {
192
        return preg_match('/{{\s*(.+?)\s*}}|{!!\s*(.+?)\s*!!}/s', $value);
193
    }
194
195
    protected function compileEchoes(string $value): string
196
    {
197
        $segments = preg_split('/({{\s*.+?\s*}}|{!!\s*.+?\s*!!})/s', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
198
199
        return Collection::make($segments)
200
            ->reject(function ($segment) {
201
                return '' === $segment;
202
            })
203
            ->map(function ($segment) {
204
                if (preg_match('/{{\s*(.+?)\s*}}/s', $segment, $matches)) {
205
                    return 'e('.$matches[1].')';
206
                }
207
208
                if (preg_match('/{!!\s*(.+?)\s*!!}/s', $segment, $matches)) {
209
                    return $matches[1];
210
                }
211
212
                return "'".str_replace("'", "\\'", $segment)."'";
213
            })
214
            ->implode('.');
215
    }
216
}
217