Completed
Push — feature/0.7.0 ( 91054f...38440f )
by Ryuichi
04:10
created

CoreModel   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 23
Bugs 0 Features 0
Metric Value
c 23
b 0
f 0
dl 0
loc 279
rs 8.6206
wmc 50
lcom 1
cbo 8

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 5 1
A __initialize() 0 14 2
A __customAnnotation() 0 4 1
B __call() 0 23 6
F __execute() 0 136 34
A beginTransaction() 0 14 3
A commit() 0 6 2
A rollback() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like CoreModel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CoreModel, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace WebStream\Core;
3
4
use WebStream\DI\Injector;
5
use WebStream\Module\Container;
6
use WebStream\Module\Utility\CommonUtils;
7
use WebStream\Module\Utility\ApplicationUtils;
8
use WebStream\Annotation\Filter;
9
use WebStream\Annotation\Base\IAnnotatable;
10
use WebStream\Database\DatabaseManager;
11
use WebStream\Database\Result;
12
use WebStream\Exception\Extend\DatabaseException;
13
use WebStream\Exception\Extend\MethodNotFoundException;
14
use Doctrine\DBAL\Connection;
15
16
/**
17
 * CoreModel
18
 * @author Ryuichi TANAKA.
19
 * @since 2012/09/01
20
 * @version 0.7
21
 */
22
class CoreModel implements CoreInterface, IAnnotatable
23
{
24
    use Injector, CommonUtils, ApplicationUtils;
25
26
    /**
27
     * @var Container コンテナ
28
     */
29
    private $container;
30
31
    /**
32
     * @var DatabaseManager データベースマネージャ
33
     */
34
    private $manager;
35
36
    /**
37
     * @var array<AnnotationContainer> クエリアノテーションリスト
38
     */
39
    private $queryAnnotations;
40
41
    /**
42
     * @var boolean オートコミットフラグ
43
     */
44
    private $isAutoCommit;
45
46
    /**
47
     * @var array<mixed> カスタムアノテーション
48
     */
49
    protected $annotation;
50
51
    /**
52
     * @var LoggerAdapter ロガー
53
     */
54
    protected $logger;
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function __destruct()
60
    {
61
        $this->logger->debug("Model end.");
62
        $this->__clear();
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     * @Filter(type="initialize")
68
     */
69
    public function __initialize(Container $container)
70
    {
71
        if ($container->connectionContainerList === null) {
0 ignored issues
show
Documentation introduced by
The property connectionContainerList does not exist on object<WebStream\Module\Container>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
72
            $this->logger->warn("Can't use database in Model Layer.");
73
74
            return;
75
        }
76
77
        $this->queryAnnotations = $container->queryAnnotations;
0 ignored issues
show
Documentation introduced by
The property queryAnnotations does not exist on object<WebStream\Module\Container>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
78
        $container->logger = $this->logger;
0 ignored issues
show
Documentation introduced by
The property logger does not exist on object<WebStream\Module\Container>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
79
        $this->manager = new DatabaseManager($container);
80
        $this->isAutoCommit = true;
81
        $this->container = $container;
82
    }
83
84
    /**
85
    * {@inheritdoc}
86
     */
87
    public function __customAnnotation(array $annotation)
88
    {
89
        $this->annotation = $annotation;
90
    }
91
92
    /**
93
     * method missing
94
     * @param string メソッド名
95
     * @param array sql/bindパラメータ
96
     */
97
    final public function __call($method, $arguments)
98
    {
99
        // DBコネクションが取得できなければエラー
100
        $filepath = debug_backtrace()[0]["file"];
101
        if (!$this->manager->loadConnection($filepath)) {
102
            throw new MethodNotFoundException("Undefined method called: $method");
103
        }
104
105
        if ($this->manager->isConnected() === false) {
106
            $this->manager->connect();
107
        }
108
109
        $result = $this->__execute($method, $arguments, $filepath);
110
111
        if ($this->isAutoCommit) {
112
            if (is_int($result) && $result > 0) {
113
                $this->manager->commit();
114
            }
115
            $this->manager->disconnect();
116
        }
117
118
        return $result;
119
    }
120
121
    /**
122
     * DB処理を実行する
123
     * @param string メソッド名
124
     * @param array sql/bindパラメータ
125
     * @param string 現在実行中のクラスのファイルパス
126
     */
127
    final public function __execute($method, $arguments, $filepath)
128
    {
129
        $result = null;
130
131
        try {
132
            if (preg_match('/^(?:select|(?:dele|upda)te|insert)$/', $method)) {
133
                $sql = $arguments[0];
134
                $bind = null;
135
                if (array_key_exists(1, $arguments)) {
136
                    $bind = $arguments[1];
137
                }
138
139
                if (is_string($sql)) {
140
                    if ($method !== 'select' && $this->isAutoCommit) {
141
                        $this->manager->beginTransaction(Connection::TRANSACTION_READ_COMMITTED);
142
                    }
143
                    if (is_array($bind)) {
144
                        $result = $this->manager->query($sql, $bind)->{$method}();
145
                    } else {
146
                        $result = $this->manager->query($sql)->{$method}();
147
                    }
148
                } else {
149
                    throw new DatabaseException("Invalid SQL or bind parameters: " . $sql .", " . strval($bind));
150
                }
151
            } else {
152
                $bind = null;
153
                if (array_key_exists(0, $arguments)) {
154
                    $bind = $arguments[0];
155
                }
156
157
                $trace = debug_backtrace();
158
                $modelMethod = null;
159
                for ($i = 0; $i < count($trace); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
160
                    if ($this->inArray($trace[$i]["function"], ["__call", "__execute"])) {
161
                        continue;
162
                    }
163
164
                    if ($trace[$i]["function"] !== null) {
165
                        $modelMethod = $trace[$i]["function"];
166
                        break;
167
                    }
168
                }
169
170
                $namespace = substr($this->getNamespace($filepath), 1);
171
                $queryKey = $namespace . "\\" . basename($filepath, ".php") . "#" . $modelMethod;
172
173
                $query = null;
174
                foreach ($this->queryAnnotations as $queryAnnotation) {
175
                    $queryFunctions = $queryAnnotation->get($queryKey);
176
177
                    if ($queryFunctions === null) {
178
                        continue;
179
                    }
180
181
                    foreach ($queryFunctions as $queryFunction) {
182
                        $xmlObjectList = $queryFunction->fetch();
183
                        foreach ($xmlObjectList as $xmlObject) {
184
                            if ($xmlObject !== null) {
185
                                $xmlElement = $xmlObject->xpath("//mapper[@namespace='$namespace']/*[@id='$method']");
186
187
                                if (!empty($xmlElement)) {
188
                                    $query = ["sql" => trim($xmlElement[0]->__toString()), "method" => $xmlElement[0]->getName()];
189
                                    $entity = $xmlElement[0]->attributes()["entity"];
190
                                    $query["entity"] = $entity !== null ? $entity->__toString() : null;
191
                                    break;
192
                                }
193
                            }
194
                        }
195
                    }
196
                }
197
198
                if ($query === null) {
199
                    throw new DatabaseException("SQL statement can't getting from xml file: " . $modelMethod);
200
                }
201
202
                $sql = $query["sql"];
203
                $method = $query["method"];
204
                $entityClassPath = $query["entity"];
205
206
                if ($entityClassPath !== null) {
207
                    if (!class_exists($entityClassPath)) {
208
                        throw new DatabaseException("Entity classpath is not found: " . $entityClassPath);
209
                    }
210
211
                    switch ($method) {
212
                        case "select":
213
                            if (is_string($sql)) {
214
                                if (is_array($bind)) {
215
                                    $result = $this->manager->query($sql, $bind)->select()->toEntity($entityClassPath);
216
                                } else {
217
                                    $result = $this->manager->query($sql)->select()->toEntity($entityClassPath);
218
                                }
219
                            } else {
220
                                $errorMessage = "Invalid SQL or bind parameters: " . $sql;
221
                                if (is_array($bind)) {
222
                                    $errorMessage .= ", " . strval($bind);
223
                                }
224
225
                                throw new DatabaseException($errorMessage);
226
                            }
227
228
                            break;
229
                        case "insert":
230
                        case "update":
231
                        case "delete":
232
                            // Not implement
233
                            throw new DatabaseException("Entity mapping is select only.");
234
                    }
235
                } else {
236
                    if (is_string($sql)) {
237
                        if ($method !== 'select' && $this->isAutoCommit) {
238
                            $this->manager->beginTransaction(Connection::TRANSACTION_READ_COMMITTED);
239
                        }
240
                        if (is_array($bind)) {
241
                            $result = $this->manager->query($sql, $bind)->{$method}();
242
                        } else {
243
                            $result = $this->manager->query($sql)->{$method}();
244
                        }
245
                    } else {
246
                        $errorMessage = "Invalid SQL or bind parameters: " . $sql;
247
                        if (is_array($bind)) {
248
                            $errorMessage .= ", " . strval($bind);
249
                        }
250
251
                        throw new DatabaseException($errorMessage);
252
                    }
253
                }
254
            }
255
        } catch (DatabaseException $e) {
256
            $this->manager->rollback();
257
            $this->manager->disconnect();
258
            throw $e;
259
        }
260
261
        return $result;
262
    }
263
264
    /**
265
     * トランザクション開始
266
     * @param int $isolationLevel トランザクション分離レベル
267
     */
268
    final public function beginTransaction(int $isolationLevel = Connection::TRANSACTION_READ_COMMITTED)
269
    {
270
        $filepath = debug_backtrace()[0]["file"];
271
        if (!$this->manager->loadConnection($filepath)) {
272
            throw new MethodNotFoundException("Undefined method called: $method");
273
        }
274
275
        if ($this->manager->isConnected() === false) {
276
            $this->manager->connect();
277
        }
278
279
        $this->manager->beginTransaction($isolationLevel);
280
        $this->isAutoCommit = false;
281
    }
282
283
    /**
284
     * コミットする
285
     */
286
    final public function commit()
287
    {
288
        if ($this->isAutoCommit === false) {
289
            $this->manager->commit();
290
        }
291
    }
292
293
    /**
294
     * ロールバックする
295
     */
296
    final public function rollback()
297
    {
298
        $this->manager->rollback();
299
    }
300
}
301