Completed
Push — master ( e62c49...fdcbe9 )
by Derek Stephen
06:37
created

ProxyGeneratorService::processClasses()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 2
1
<?php
2
/**
3
 * User: delboy1978uk
4
 * Date: 27/11/2016
5
 * Time: 15:48
6
 */
7
8
namespace Del\ProxyGenerator\Service;
9
10
use InvalidArgumentException;
11
use League\Flysystem\Filesystem;
12
use League\Flysystem\Adapter\Local;
13
14
class ProxyGeneratorService
15
{
16
    const REGEX_CLASS = '#class\s(?<class>\w+)(\sextends\s(?<parentClass>\w+))?(\simplements\s(?<interface>\w+))?#';
17
    const REGEX_NAMESPACE = '#namespace\s(?<namespace>\w+.*)\;#';
18
    const PROCESS_INTERFACES = 'implements';
19
    const PROCESS_CHILD_CLASSES = 'extends';
20
21
    const TEMPLATE = <<<HERE
22
<?php
23
24
namespace REPLACE_NAMESPACE;
25
26
REPLACE_USE
27
28
REPLACE_ABSTRACTclass REPLACE_CLASS REPLACE_EXTENDS REPLACE_IMPLEMENTS
29
{
30
}
31
HERE;
32
33
34
    /** @var  Filesystem */
35
    private $fileSystem;
36
37
    /** @var string $scanPath  */
38
    private $scanPath;
39
40
    /** @var string $scanPath  */
41
    private $scanInterface;
42
43
    /** @var string $replaceInterface */
44
    private $replaceInterface;
45
46
    /** @var string $targetPath */
47
    private $targetPath;
48
49
    /** @var string $targetNamespace */
50
    private $targetNamespace;
51
52
    /** @var  $replaceNamespace */
53
    private $replaceNamespace;
54
55
    /** @var array $implementingClasses */
56
    private $implementingClasses = [];
57
58
    /** @var array $extendingClasses */
59
    private $extendingClasses = [];
60
61
    private $type = self::PROCESS_INTERFACES;
62
63
    /** @var array $currentFile */
64
    private $currentFile;
65
66
    /** @var string $currentNamespace */
67
    private $currentNamespace;
68
69
    /** @var string $checkClass  */
70
    private $checkClass;
71
72
    private $newMatches = false;
73
74
    private $baseNamespace;
75
76
    public function __construct($projectRootPath = '.')
77
    {
78
        $adapter = new Local($projectRootPath);
79
        $this->fileSystem = new Filesystem($adapter);
80
        $this->targetPath = 'src';
81
    }
82
83
    /**
84
     * @param string $scanPath
85
     */
86
    public function setScanPath($scanPath)
87
    {
88
        $this->scanPath = $scanPath;
89
    }
90
91
    /**
92
     * @param string $scanInterface
93
     */
94
    public function setScanInterface($scanInterface)
95
    {
96
        $this->scanInterface = $scanInterface;
97
    }
98
99
    /**
100
     * @param string $replaceInterface
101
     */
102
    public function setReplaceInterface($replaceInterface)
103
    {
104
        $this->replaceInterface = $replaceInterface;
105
    }
106
107
    /**
108
     * @param string $targetPath
109
     * @return ProxyGeneratorService
110
     */
111
    public function setTargetPath($targetPath)
112
    {
113
        $this->targetPath = $targetPath;
114
        return $this;
115
    }
116
117
    /**
118
     * @param string $targetNamespace
119
     * @return ProxyGeneratorService
120
     */
121
    public function setTargetNamespace($targetNamespace)
122
    {
123
        $this->targetNamespace = $targetNamespace;
124
        return $this;
125
    }
126
127
    /**
128
     * @param mixed $replaceNamespace
129
     * @return ProxyGeneratorService
130
     */
131
    public function setReplaceNamespace($replaceNamespace)
132
    {
133
        $this->replaceNamespace = $replaceNamespace;
134
        return $this;
135
    }
136
137
138
139
    /**
140
     * @return array
141
     */
142
    public function generate()
143
    {
144
        if(empty($this->scanPath) || empty($this->scanInterface) || empty($this->replaceInterface) || empty($this->targetNamespace) || empty($this->replaceNamespace)) {
145
            throw new InvalidArgumentException('set scanPath, scanInterface, replaceInterface, targetNamespace and $replaceNamespace');
146
        }
147
        $ex = explode('\\', $this->scanInterface);
148
        $this->scanInterface = array_pop($ex);
149
        $this->baseNamespace = implode('\\', $ex);
150
        $classes = $this->getClasses();
151
        $classes = $this->generateFiles($classes);
152
        return $classes;
153
    }
154
155
    /**
156
     * @param $interface
157
     * @return array
158
     */
159
    private function getClasses()
160
    {
161
        // Get all the files to scan
162
        $contents = $this->fileSystem->listContents($this->scanPath, true);
163
        $classes = $this->processContents($contents);
164
        return $classes;
165
    }
166
167
    /**
168
     * @param $contents
169
     * @param $interfaceName
170
     * @return array
171
     */
172
    private function processContents($contents)
173
    {
174
        // Find all classes implementing $interfaceName
175
        $this->type = self::PROCESS_INTERFACES;
176
        foreach ($contents as $file) {
177
            $this->processFile($file);
178
        }
179
        $this->type = self::PROCESS_CHILD_CLASSES;
180
        $this->processClasses($this->implementingClasses, $contents);
181
        do {
182
            $this->newMatches = false;
183
            $this->processClasses($this->extendingClasses, $contents);
184
            $keepGoing = $this->newMatches;
185
        } while ($keepGoing === true);
186
187
        return array_merge($this->implementingClasses, $this->extendingClasses);
188
    }
189
190
    private function processClasses($classes, $contents)
191
    {
192
        foreach ($classes as $class) {
193
            $this->checkClass = $class['class'];
194
            foreach($contents as $file) {
195
                $this->processFile($file);
196
            }
197
        }
198
    }
199
200
    /**
201
     * @param $file
202
     * @return bool
203
     */
204
    private function processFile($file)
205
    {
206
        $this->currentFile = $file;
207
        if (!isset($file['extension']) || $file['extension'] != 'php') {
208
            return false;
209
        }
210
        $filePath = $file['path'];
211
        $contents = $this->fileSystem->read($filePath);
212
        foreach (explode("\n", $contents) as $line) {
213
            $this->processLine($line);
214
        }
215
        return true;
216
    }
217
218
    private function processLine($line)
219
    {
220
        preg_match(self::REGEX_NAMESPACE, $line, $match);
221
        if (!empty($match['namespace'])) {
222
            $this->currentNamespace = $match['namespace'];
223
        }
224
        preg_match(self::REGEX_CLASS, $line, $match);
225
        if (!empty($match)) {
226
            $match['abstract'] = (strstr($line, 'abstract ')) ? true : false;
227
            $this->processMatch($match);
228
        }
229
    }
230
231
    private function processMatch(array $match)
232
    {
233
        switch ($this->type) {
234
            case self::PROCESS_CHILD_CLASSES:
235
                $this->processExtendingClass($match);
236
                break;
237
            case self::PROCESS_INTERFACES:
238
            default:
239
                $this->processImplementingClass($match);
240
                break;
241
        }
242
    }
243
244
    private function processImplementingClass(array $match)
245
    {
246
        if(!empty($match['interface']) && $match['interface'] == $this->scanInterface) {
247
            $this->currentFile['interface'] = $match['interface'];
248
            $this->currentFile['abstract'] = $match['abstract'];
249
            $this->currentFile['class'] = $match['class'];
250
            $this->currentFile['parentClass'] = isset($match['parentClass']) ? $match['parentClass'] : null;
251
            $this->currentFile['namespace'] = $this->currentNamespace;
252
            if(!in_array($this->implementingClasses, $this->currentFile)) {
253
                $this->implementingClasses[] = $this->currentFile;
254
            }
255
        }
256
    }
257
258
    private function processExtendingClass(array $match)
259
    {
260
        if(isset($match['parentClass']) && $this->checkClass == $match['parentClass']) {
261
            $this->currentFile['abstract'] = $match['abstract'];
262
            $this->currentFile['class'] = $match['class'];
263
            $this->currentFile['parentClass'] = $match['parentClass'];
264
            $this->currentFile['namespace'] = $this->currentNamespace;
265
            $key = $this->currentNamespace.'\\'.$this->currentFile['class'];
266
            if(!isset($this->extendingClasses[$key])) {
267
                $this->extendingClasses[$key] = $this->currentFile;
268
                $this->newMatches = true;
269
            }
270
        }
271
    }
272
273
274
    private function generateFiles($classes)
275
    {
276
        $newClasses = [];
277
        foreach ($classes as $class) {
278
279
            $file = self::TEMPLATE;
280
            $namespace = isset($class['namespace']) ? $class['namespace'] : null;
281
            $extraNamespace = str_replace($this->targetNamespace, '', $namespace);
282
            $alias = 'ThirdParty'.$class['class'];
283
            $use = 'use '.$class['namespace'].'\\'.$class['class'].' as '.$alias.';';
284
            $abstract = $class['abstract'] ? 'abstract ' : null;
285
            $extends = 'extends '.$alias;
286
287
            $use .= ($extraNamespace != '') ? "\n".'use '.$this->replaceInterface.';' : null;
288
            $interface = str_replace($this->replaceNamespace.'\\', '',$this->replaceInterface);
289
            $implements = 'implements ' . $interface;
290
291
292
            $file = str_replace('REPLACE_NAMESPACE', $this->replaceNamespace.$extraNamespace, $file);
293
            $file = str_replace('REPLACE_USE', $use, $file);
294
            $file = str_replace('REPLACE_ABSTRACT', $abstract, $file);
295
            $file = str_replace('REPLACE_CLASS', $class['class'], $file);
296
            $file = str_replace('REPLACE_EXTENDS', $extends, $file);
297
            $file = str_replace('REPLACE_IMPLEMENTS', $implements, $file);
298
299
            $extraPath = !empty($extraNamespace) ? str_replace('\\', '', $extraNamespace).DIRECTORY_SEPARATOR : null;
300
            $location = $this->targetPath.DIRECTORY_SEPARATOR.$extraPath.$class['class'].'.php';
301
            if(!$this->fileSystem->has($location)) {
302
                $this->fileSystem->write($location, $file);
303
            }
304
            $newClasses[] = $location;
305
        }
306
        return $newClasses;
307
    }
308
}