Passed
Push — master ( 718619...84b65d )
by Serhii
06:42 queued 13s
created

SearchableListFactory::getStmts()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3.072

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 15
c 2
b 0
f 0
nc 3
nop 0
dl 0
loc 22
ccs 12
cts 15
cp 0.8
crap 3.072
rs 9.7666
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Matchish\ScoutElasticSearch\Searchable;
6
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Str;
9
use Laravel\Scout\Searchable;
10
use PhpParser\Error;
11
use PhpParser\Node;
12
use PhpParser\Node\Stmt\Class_;
13
use PhpParser\NodeFinder;
14
use PhpParser\NodeTraverser;
15
use PhpParser\NodeVisitor\NameResolver;
16
use PhpParser\ParserFactory;
17
use Roave\BetterReflection\BetterReflection;
18
use Roave\BetterReflection\Reflector\ClassReflector;
19
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
20
use Symfony\Component\Finder\Finder;
21
22
final class SearchableListFactory
23
{
24
    /**
25
     * @var array
26
     */
27
    private static $searchableClasses;
28
    /**
29
     * @var string
30
     */
31
    private $namespace;
32
    /**
33
     * @var string
34
     */
35
    private $appPath;
36
    /**
37
     * @var array
38
     */
39
    private $errors = [];
40
    /**
41
     * @var ClassReflector
42
     */
43
    private $classReflector;
44
45
    /**
46
     * @param string $namespace
47
     */
48 12
    public function __construct(string $namespace, string $appPath)
49
    {
50 12
        $this->namespace = $namespace;
51 12
        $this->appPath = $appPath;
52 12
    }
53
54
    /**
55
     * @return array
56
     */
57
    public function getErrors(): array
58
    {
59
        return $this->errors;
60
    }
61
62
    /**
63
     * @return Collection
64
     */
65 12
    public function make(): Collection
66
    {
67 12
        return new Collection($this->find());
68
    }
69
70
    /**
71
     * Get a list of searchable models.
72
     *
73
     * @return string[]
74
     */
75 12
    private function find(): array
76
    {
77 12
        $appNamespace = $this->namespace;
78
79 12
        return array_values(array_filter($this->getSearchableClasses(), function (string $class) use ($appNamespace) {
80 12
            return Str::startsWith($class, $appNamespace);
81 12
        }));
82
    }
83
84
    /**
85
     * @return string[]
86
     */
87 12
    private function getSearchableClasses(): array
88
    {
89 12
        if (self::$searchableClasses === null) {
0 ignored issues
show
introduced by
The condition self::searchableClasses === null is always false.
Loading history...
90 1
            $projectClasses = $this->getProjectClasses();
91
92 1
            self::$searchableClasses = $projectClasses->filter(function ($class) {
93 1
                return $this->findSearchableTraitRecursively($class);
94 1
            })->toArray();
95
        }
96
97 12
        return self::$searchableClasses;
98
    }
99
100
    /**
101
     * @return Collection
102
     */
103 1
    private function getProjectClasses(): Collection
104
    {
105 1
        $nodeFinder = new NodeFinder();
106
        /** @var Class_[] $nodes */
107 1
        $nodes = $nodeFinder->find($this->getStmts(), function (Node $node) {
108 1
            return $node instanceof Class_;
109 1
        });
110
111 1
        return Collection::make($nodes)->map(function ($node) {
112 1
            return $node->namespacedName->toCodeString();
113 1
        });
114
    }
115
116
    /**
117
     * @return array
118
     */
119 1
    private function getStmts(): array
120
    {
121 1
        $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
122 1
        $nameResolverVisitor = new NameResolver();
123 1
        $nodeTraverser = new NodeTraverser();
124 1
        $nodeTraverser->addVisitor($nameResolverVisitor);
125 1
        $stmts = [];
126 1
        $finder = Finder::create()->files()->name('*.php')->in($this->appPath);
127
128 1
        foreach ($finder as $file) {
129
            try {
130 1
                $stmts[] = $parser->parse($file->getContents());
131
            } catch (Error $e) {
132
                $this->errors[] = $e->getMessage();
133
                continue;
134
            }
135
        }
136
137 1
        $stmts = Collection::make($stmts)->flatten(1)->toArray();
138 1
        $stmts = $nodeTraverser->traverse($stmts);
139
140 1
        return $stmts;
141
    }
142
143
    /**
144
     * @param string $class
145
     * @return bool
146
     */
147 1
    private function findSearchableTraitRecursively(string $class): bool
148
    {
149
        try {
150 1
            $reflection = $this->classReflector()->reflect($class);
151
152 1
            if (in_array(Searchable::class, $reflection->getTraitNames())) {
153 1
                return true;
154
            }
155
156 1
            if ($parent = $reflection->getParentClass()) {
157 1
                return $this->findSearchableTraitRecursively($parent->getName());
158
            }
159
160 1
            return false;
161 1
        } catch (IdentifierNotFound $e) {
162 1
            $this->errors[] = $e->getMessage();
163
164 1
            return false;
165
        }
166
    }
167
168
    /**
169
     * @return ClassReflector
170
     */
171 1
    private function classReflector(): ClassReflector
172
    {
173 1
        if (null === $this->classReflector) {
174 1
            $this->classReflector = (new BetterReflection())->classReflector();
175
        }
176
177 1
        return $this->classReflector;
178
    }
179
}
180