Passed
Pull Request — master (#1)
by David
02:06
created

ServiceProvider::dumpServiceProviderHelper()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 52
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 30
nc 10
nop 1
dl 0
loc 52
rs 8.6868
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\Funky;
5
6
use Doctrine\Common\Annotations\AnnotationReader;
7
use Interop\Container\ServiceProviderInterface;
8
use ReflectionClass;
9
use TheCodingMachine\Funky\Annotations\Factory;
10
use TheCodingMachine\Funky\Utils\FileSystem;
11
12
class ServiceProvider implements ServiceProviderInterface
13
{
14
    /**
15
     * @var ReflectionClass
16
     */
17
    private $refClass;
18
19
    private static $annotationReader;
20
21
22
    private static function getAnnotationReader() : AnnotationReader
23
    {
24
        if (self::$annotationReader === null) {
25
            self::$annotationReader = new AnnotationReader();
26
        }
27
        return self::$annotationReader;
28
    }
29
30
    /**
31
     * @return FactoryDefinition[]
32
     * @throws \TheCodingMachine\Funky\BadModifierException
33
     */
34
    private function getFactoryDefinitions(): array
35
    {
36
        $refClass = new ReflectionClass($this);
37
        $factories = [];
38
39
        foreach ($refClass->getMethods() as $method) {
40
            $factoryAnnotation = self::getAnnotationReader()->getMethodAnnotation($method, Factory::class);
41
            if ($factoryAnnotation) {
42
                $factories[] = new FactoryDefinition($method, $factoryAnnotation);
43
            }
44
        }
45
46
        return $factories;
47
    }
48
49
    /**
50
     * Returns a list of all container entries registered by this service provider.
51
     *
52
     * - the key is the entry name
53
     * - the value is a callable that will return the entry, aka the **factory**
54
     *
55
     * Factories have the following signature:
56
     *        function(\Psr\Container\ContainerInterface $container)
57
     *
58
     * @return callable[]
59
     */
60
    public function getFactories()
61
    {
62
        [$className, $fileName] = $this->getFileAndClassName();
63
        if (!file_exists($fileName) || filemtime($this->getRefClass()->getFileName()) > filemtime($fileName)) {
64
            $this->dumpHelper();
65
        }
66
67
        require_once $fileName;
68
69
        $factoriesCallable = $className.'::getFactories';
70
71
        return $factoriesCallable();
72
    }
73
74
    /**
75
     * Writes the helper class and returns the file path.
76
     *
77
     * @return string
78
     * @throws \TheCodingMachine\Funky\IoException
79
     */
80
    public function dumpHelper(): string
81
    {
82
        [$className, $tmpFile] = $this->getFileAndClassName();
83
84
        FileSystem::mkdir(dirname($tmpFile));
85
        $result = file_put_contents($tmpFile, $this->dumpServiceProviderHelper($className));
86
        if ($result === false) {
87
            throw IoException::cannotWriteFile($tmpFile);
88
        }
89
        return $tmpFile;
90
    }
91
92
    /**
93
     * @return string[] Returns an array with 2 items: the class name and the file name.
94
     */
95
    private function getFileAndClassName(): array
96
    {
97
        $refClass = $this->getRefClass();
98
        $className = $this->getClassName();
99
100
        $fileName = sys_get_temp_dir().'/funky_cache/'.str_replace(':', '', dirname($refClass->getFileName()).'/'.str_replace('\\', '__', $className).'.php');
101
102
        return [$className, $fileName];
103
    }
104
105
    private function getRefClass(): ReflectionClass
106
    {
107
        if ($this->refClass === null) {
108
            $this->refClass = new ReflectionClass($this);
109
        }
110
        return $this->refClass;
111
    }
112
113
    private function getClassName(): string
114
    {
115
        $className = get_class($this).'Helper';
116
        if ($this->getRefClass()->isAnonymous()) {
0 ignored issues
show
Bug introduced by
The method isAnonymous() does not exist on ReflectionClass. It seems like you code against a sub-type of ReflectionClass such as Roave\BetterReflection\R...Adapter\ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
        if ($this->getRefClass()->/** @scrutinizer ignore-call */ isAnonymous()) {
Loading history...
117
            $className = preg_replace("/[^A-Za-z0-9_\x7f-\xff ]/", '', $className);
118
        }
119
        return $className;
120
    }
121
122
    /**
123
     * Returns the code of a "service provider helper class" that contains generated factory code.
124
     *
125
     * @return string
126
     */
127
    private function dumpServiceProviderHelper(string $className): string
128
    {
129
        $slashPos = strrpos($className, '\\');
130
        if ($slashPos !== false) {
131
            $namespace = 'namespace '.substr($className, 0, $slashPos).";\n";
132
            $shortClassName = substr($className, $slashPos+1);
133
        } else {
134
            $namespace = null;
135
            $shortClassName = $className;
136
        }
137
138
        $factoriesArrayCode = [];
139
        $factories = [];
140
        $factoryCount = 0;
141
        foreach ($this->getFactoryDefinitions() as $definition) {
142
            if ($definition->isPsrFactory()) {
143
                $factoriesArrayCode[] = '            '.var_export($definition->getName(), true).' => ['.var_export($definition->getReflectionMethod()->getDeclaringClass()->getName(), true).', '.var_export($definition->getReflectionMethod()->getName(), true)."],\n";
144
            } else {
145
                $factoryCount++;
146
                $localFactoryName = 'factory'.$factoryCount;
147
                $factoriesArrayCode[] = '            '.var_export($definition->getName(), true).' => [self::class, '.var_export($localFactoryName, true)."],\n";
148
                $factories[] = $definition->buildFactoryCode($localFactoryName);
149
            }
150
            foreach ($definition->getAliases() as $alias) {
151
                $factoriesArrayCode[] = '            '.var_export($alias, true).' => new Alias('.var_export($definition->getName(), true)."),\n";
152
            }
153
        }
154
155
        $factoriesArrayStr = implode("\n", $factoriesArrayCode);
156
        $factoriesStr = implode("\n", $factories);
157
158
        $code = <<<EOF
159
<?php
160
$namespace
161
162
use Interop\Container\Factories\Alias;
163
use Psr\Container\ContainerInterface;
164
165
final class $shortClassName
166
{
167
    public static function getFactories(): array
168
    {
169
        return [
170
$factoriesArrayStr
171
        ];
172
    }
173
    
174
$factoriesStr
175
}
176
EOF;
177
178
        return $code;
179
    }
180
181
    /**
182
     * Returns a list of all container entries extended by this service provider.
183
     *
184
     * - the key is the entry name
185
     * - the value is a callable that will return the modified entry
186
     *
187
     * Callables have the following signature:
188
     *        function(Psr\Container\ContainerInterface $container, $previous)
189
     *     or function(Psr\Container\ContainerInterface $container, $previous = null)
190
     *
191
     * About factories parameters:
192
     *
193
     * - the container (instance of `Psr\Container\ContainerInterface`)
194
     * - the entry to be extended. If the entry to be extended does not exist and the parameter is nullable,
195
     *   `null` will be passed.
196
     *
197
     * @return callable[]
198
     */
199
    public function getExtensions()
200
    {
201
        // TODO: Implement getExtensions() method.
202
    }
203
}
204