Completed
Pull Request — 1.1 (#41)
by
unknown
06:51
created

FluentDriver::loadMetadataForClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
crap 1
1
<?php
2
3
namespace LaravelDoctrine\Fluent;
4
5
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
6
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
7
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
8
use Doctrine\ORM\Mapping\MappingException;
9
use InvalidArgumentException;
10
use LaravelDoctrine\Fluent\Builders\Builder;
11
use LaravelDoctrine\Fluent\Mappers\MapperSet;
12
13
class FluentDriver implements MappingDriver
14
{
15
    /**
16
     * @var MapperSet
17
     */
18
    protected $mappers;
19
20
    /**
21
     * @var callable
22
     */
23
    protected $fluentFactory;
24
25
    /**
26
     * Initializes a new FileDriver that looks in the given path(s) for mapping
27
     * documents and operates in the specified operating mode.
28
     *
29
     * @param string[]|null $mappings
30
     * @param string[]|null $paths
31
     *
32
     * @throws \Doctrine\ORM\Mapping\MappingException
33 4
     */
34 4
    public function __construct($mappings, $paths)
35
    {
36
        $this->fluentFactory = function (ClassMetadata $metadata) {
37 92
            return new Builder(new ClassMetadataBuilder($metadata));
38 92
        };
39 92
40
        $this->mappers = new MapperSet();
41
42
        if ($mappings !== null) {
43
            $this->addMappings($mappings);
44
        }
45
46
        if ($paths !== null) {
47 6
            $this->addPaths($paths);
48
        }
49 6
    }
50 5
51 5
    /**
52 5
     * Loads the metadata for the specified class into the provided container.
53
     *
54
     * @param string        $className
55
     * @param ClassMetadata $metadata
56
     */
57
    public function loadMetadataForClass($className, ClassMetadata $metadata)
58
    {
59
        $this->mappers->getMapperFor($className)->map(
60 12
            $this->getFluent($metadata)
61
        );
62 12
    }
63
64
    /**
65
     * Gets the names of all mapped classes known to this driver.
66
     *
67
     * @throws MappingException
68
     *
69
     * @return string[] The names of all mapped classes known to this driver.
70
     */
71
    public function getAllClassNames()
72
    {
73 4
        return $this->mappers->getClassNames();
74
    }
75
76 4
    /**
77 4
     * Returns whether the class with the specified name should have its metadata loaded.
78
     * This is only the case if it is either mapped as an Entity or a MappedSuperclass.
79
     *
80
     * @param string $className
81
     *
82
     * @return bool
83 92
     */
84
    public function isTransient($className)
85 92
    {
86 82
        return
87 1
            !$this->mappers->hasMapperFor($className) ||
88
            $this->mappers->getMapperFor($className)->isTransient();
89
    }
90 81
91
    /**
92 81
     * @param string[] $mappings
93 1
     */
94
    public function addMappings(array $mappings = [])
95
    {
96 80
        foreach ($mappings as $class) {
97 92
            if (!class_exists($class)) {
98 92
                throw new InvalidArgumentException("Mapping class [{$class}] does not exist");
99
            }
100
101
            $mapping = new $class();
102
103
            if (!$mapping instanceof Mapping) {
104
                throw new InvalidArgumentException("Mapping class [{$class}] should implement ".Mapping::class);
105
            }
106 87
107
            $this->addMapping($mapping);
108 87
        }
109 87
    }
110
111
    /**
112
     * @param Mapping $mapping
113
     *
114 5
     * @throws MappingException
115
     */
116 5
    public function addMapping(Mapping $mapping)
117
    {
118
        $this->mappers->add($mapping);
119
    }
120
121
    /**
122
     * @return MapperSet
123
     */
124
    public function getMappers()
125
    {
126 1
        return $this->mappers;
127
    }
128 1
129 1
    /**
130
     * Add mappings from an array of folders.
131
     *
132
     * @param string[] $paths
133
     *
134
     * @throws MappingException
135 5
     */
136
    public function addPaths($paths)
137 5
    {
138
        foreach ($paths as $path) {
139
            if (!is_dir($path)) {
140
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
141
            }
142
143
            $iterator = new \RecursiveIteratorIterator(
144
                new \RecursiveDirectoryIterator($path),
145
                \RecursiveIteratorIterator::LEAVES_ONLY
146
            );
147
148
            foreach ($iterator as $file) {
149
                if ($file->getBasename('.php') == $file->getBasename()) {
150
                    continue;
151
                }
152
153
                $sourceFile = realpath($file->getPathName());
154
                $className = $this->getClassFromFile($sourceFile);
155
                $this->addMapping(new $className());
156
            }
157
        }
158
    }
159
160
    /**
161
     * Get the FQN of a class from a source file.
162
     *
163
     * @param $pathToFile
164
     *
165
     * @return string
166
     */
167
    private function getClassFromFile($pathToFile)
168
    {
169
        //http://jarretbyrne.com/2015/06/197/
170
        //Grab the contents of the file
171
        $contents = file_get_contents($pathToFile);
172
173
        //Start with a blank namespace and class
174
        $namespace = $class = '';
175
176
        //Set helper values to know that we have found the namespace/class token and need to collect the string values after them
177
        $getting_namespace = $getting_class = false;
178
179
        //Go through each token and evaluate it as necessary
180
        foreach (token_get_all($contents) as $token) {
181
182
            //If this token is the namespace declaring, then flag that the next tokens will be the namespace name
183
            if (is_array($token) && $token[0] == T_NAMESPACE) {
184
                $getting_namespace = true;
185
            }
186
187
            //If this token is the class declaring, then flag that the next tokens will be the class name
188
            if (is_array($token) && $token[0] == T_CLASS) {
189
                $getting_class = true;
190
            }
191
192
            //While we're grabbing the namespace name...
193
            if ($getting_namespace === true) {
194
195
                //If the token is a string or the namespace separator...
196
                if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
197
198
                    //Append the token's value to the name of the namespace
199
                    $namespace .= $token[1];
200
                } elseif ($token === ';') {
201
202
                    //If the token is the semicolon, then we're done with the namespace declaration
203
                    $getting_namespace = false;
204
                }
205
            }
206
207
            //While we're grabbing the class name...
208
            if ($getting_class === true) {
209
210
                //If the token is a string, it's the name of the class
211
                if (is_array($token) && $token[0] == T_STRING) {
212
213
                    //Store the token's value as the class name
214
                    $class = $token[1];
215
216
                    //Got what we need, stope here
217
                    break;
218
                }
219
            }
220
        }
221
222
        //Build the fully-qualified class name and return it
223
        return $namespace ? $namespace.'\\'.$class : $class;
224
    }
225
226
    /**
227
     * Override the default Fluent factory method with a custom one.
228
     * Use this to implement your own Fluent builder.
229
     * The method will receive a ClassMetadata object as its only argument.
230
     *
231
     * @param callable $factory
232
     */
233
    public function setFluentFactory(callable $factory)
234
    {
235
        $this->fluentFactory = $factory;
236
    }
237
238
    /**
239
     * @param ClassMetadata $metadata
240
     *
241
     * @return Fluent
242
     */
243
    protected function getFluent(ClassMetadata $metadata)
244
    {
245
        return call_user_func($this->fluentFactory, $metadata);
246
    }
247
}
248