Completed
Push — 4.0 ( 87d096...bcc1be )
by Kiyotaka
05:44 queued 11s
created

ORM/Mapping/Driver/ReloadSafeAnnotationDriver.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Doctrine\ORM\Mapping\Driver;
15
16
use Doctrine\ORM\Mapping\MappingException;
17
use Eccube\Util\StringUtil;
18
use PhpCsFixer\Tokenizer\Token;
19
use PhpCsFixer\Tokenizer\Tokens;
20
21
/**
22
 * 同じプロセス内で新しく生成されたProxyクラスからマッピングメタデータを抽出するためのAnnotationDriver.
23
 *
24
 * 同じプロセス内で、Proxy元のEntityがロードされた後に同じFQCNを持つProxyをロードしようとすると、Fatalエラーが発生する.
25
 * このエラーを回避するために、新しく生成されたProxyクラスは一時的にクラス名を変更してからロードして、マッピングメタデータを抽出する.
26
 */
27
class ReloadSafeAnnotationDriver extends AnnotationDriver
28
{
29
    /**
30
     * @var array 新しく生成されたProxyファイルのリスト
31
     */
32
    protected $newProxyFiles;
33
34
    protected $outputDir;
35
36
    public function setNewProxyFiles($newProxyFiles)
37
    {
38
        $this->newProxyFiles = array_map(function ($file) {
39
            return realpath($file);
40
        }, $newProxyFiles);
41
    }
42
43
    /**
44
     * @param string $outputDir
45
     */
46
    public function setOutputDir($outputDir)
47
    {
48
        $this->outputDir = $outputDir;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function getAllClassNames()
55
    {
56
        if ($this->classNames !== null) {
57
            return $this->classNames;
58
        }
59
60
        if (!$this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
61
            throw MappingException::pathRequired();
62
        }
63
64
        foreach ($this->paths as $path) {
65
            if (!is_dir($path)) {
66
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
67
            }
68
69
            $iterator = new \RegexIterator(
70
                new \RecursiveIteratorIterator(
71
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
72
                    \RecursiveIteratorIterator::LEAVES_ONLY
73
                ),
74
                '/^.+'.preg_quote($this->fileExtension).'$/i',
75
                \RecursiveRegexIterator::GET_MATCH
76
            );
77
78
            foreach ($iterator as $file) {
79
                $sourceFile = $file[0];
80
81
                if (!preg_match('(^phar:)i', $sourceFile)) {
82
                    $sourceFile = realpath($sourceFile);
83
                }
84
85 View Code Duplication
                foreach ($this->excludePaths as $excludePath) {
86
                    $exclude = str_replace('\\', '/', realpath($excludePath));
87
                    $current = str_replace('\\', '/', $sourceFile);
88
89
                    if (strpos($current, $exclude) !== false) {
90
                        continue 2;
91
                    }
92
                }
93 View Code Duplication
                if ('\\' === DIRECTORY_SEPARATOR) {
94
                    $path = str_replace('\\', '/', $path);
95
                    $this->trait_proxies_directory = str_replace('\\', '/', $this->trait_proxies_directory);
96
                    $sourceFile = str_replace('\\', '/', $sourceFile);
97
                }
98
                $proxyFile = str_replace($path, $this->trait_proxies_directory, $sourceFile);
99
                if (file_exists($proxyFile)) {
100
                    $sourceFile = $proxyFile;
101
                }
102
103
                $this->classNames = array_merge($this->classNames ?: [], $this->getClassNamesFromTokens($sourceFile));
104
            }
105
        }
106
107
        return $this->classNames;
108
    }
109
110
    /**
111
     * ソースコードを字句解析してクラス名を解決します.
112
     * 新しく生成されたProxyクラスの場合は、一時的にクラス名を変更したクラスを生成してロードします.
113
     *
114
     * @param $sourceFile string ソースファイル
115
     *
116
     * @return array ソースファイルに含まれるクラス名のリスト
117
     */
118
    private function getClassNamesFromTokens($sourceFile)
119
    {
120
        $tokens = Tokens::fromCode(file_get_contents($sourceFile));
121
        $results = [];
122
        $currentIndex = 0;
123
        while ($currentIndex = $tokens->getNextTokenOfKind($currentIndex, [[T_CLASS]])) {
124
            $classNameTokenIndex = $tokens->getNextMeaningfulToken($currentIndex);
125
            if ($classNameTokenIndex) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classNameTokenIndex of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
126
                $namespaceIndex = $tokens->getNextTokenOfKind(0, [[T_NAMESPACE]]);
127
                if ($namespaceIndex) {
128
                    $namespaceEndIndex = $tokens->getNextTokenOfKind($namespaceIndex, [';']);
129
                    $namespace = $tokens->generatePartialCode($tokens->getNextMeaningfulToken($namespaceIndex), $tokens->getPrevMeaningfulToken($namespaceEndIndex));
130
                    $className = $tokens[$classNameTokenIndex]->getContent();
131
                    $fqcn = $namespace.'\\'.$className;
132
                    if (class_exists($fqcn) && !$this->isTransient($fqcn)) {
133
                        $sourceFile = realpath($sourceFile);
134
                        if (in_array($sourceFile, $this->newProxyFiles)) {
135
                            $newClassName = $className.StringUtil::random(12);
136
                            $tokens[$classNameTokenIndex] = new Token([T_STRING, $newClassName]);
137
                            $newFilePath = $this->outputDir."${newClassName}.php";
138
                            file_put_contents($newFilePath, $tokens->generateCode());
139
                            require_once $newFilePath;
140
                            $results[] = $namespace."\\${newClassName}";
141
                        } else {
142
                            $results[] = $fqcn;
143
                        }
144
                    }
145
                }
146
            }
147
            $currentIndex++;
148
        }
149
150
        return $results;
151
    }
152
}
153