This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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 | * Copyright 2016 Inneair |
||
5 | * |
||
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
7 | * you may not use this file except in compliance with the License. |
||
8 | * You may obtain a copy of the License at |
||
9 | * |
||
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
11 | * |
||
12 | * Unless required by applicable law or agreed to in writing, software |
||
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
15 | * See the License for the specific language governing permissions and |
||
16 | * limitations under the License. |
||
17 | * |
||
18 | * @license http://www.apache.org/licenses/LICENSE-2.0.html Apache-2.0 |
||
19 | */ |
||
20 | |||
21 | namespace Inneair\TransactionBundle\Aop; |
||
22 | |||
23 | use CG\Proxy\MethodInvocation; |
||
24 | use CG\Proxy\MethodInterceptorInterface; |
||
25 | use Doctrine\Common\Annotations\Reader; |
||
26 | use Doctrine\ORM\EntityManagerInterface; |
||
27 | use Exception; |
||
28 | use Psr\Log\LoggerInterface; |
||
29 | use ReflectionMethod; |
||
30 | use Inneair\TransactionBundle\Annotation\Transactional; |
||
31 | use Inneair\TransactionBundle\DependencyInjection\Configuration; |
||
32 | use Symfony\Bridge\Doctrine\RegistryInterface; |
||
33 | use Symfony\Component\DependencyInjection\ContainerAwareInterface; |
||
34 | use Symfony\Component\DependencyInjection\ContainerAwareTrait; |
||
35 | |||
36 | /** |
||
37 | * AOP advice for transaction management in services. |
||
38 | */ |
||
39 | class TransactionalInterceptor implements MethodInterceptorInterface, ContainerAwareInterface |
||
40 | { |
||
41 | use ContainerAwareTrait; |
||
42 | |||
43 | /** |
||
44 | * Doctrine's entity manager registry. |
||
45 | * @var RegistryInterface |
||
46 | */ |
||
47 | private $entityManagerRegistry; |
||
48 | /** |
||
49 | * Annotations reader. |
||
50 | * @var Reader |
||
51 | */ |
||
52 | private $reader; |
||
53 | /** |
||
54 | * Logger. |
||
55 | * @var LoggerInterface |
||
56 | */ |
||
57 | private $logger; |
||
58 | |||
59 | /** |
||
60 | * Creates a method interceptor managing the @Transactional annotation. |
||
61 | * |
||
62 | * @param RegistryInterface $entityManagerRegistry Doctrine's entity manager registry. |
||
63 | * @param Reader $reader Doctrine Annotation reader. |
||
64 | * @param LoggerInterface $logger Logger. |
||
65 | */ |
||
66 | 13 | public function __construct(RegistryInterface $entityManagerRegistry, Reader $reader, LoggerInterface $logger) |
|
67 | { |
||
68 | 13 | $this->entityManagerRegistry = $entityManagerRegistry; |
|
69 | 13 | $this->reader = $reader; |
|
70 | 13 | $this->logger = $logger; |
|
71 | 13 | } |
|
72 | |||
73 | /** |
||
74 | * {@inheritDoc} |
||
75 | * |
||
76 | * @param MethodInvocation $method Current method invocation. |
||
77 | */ |
||
78 | 13 | public function intercept(MethodInvocation $method) |
|
79 | { |
||
80 | 13 | $methodDefinition = $method->reflection; |
|
81 | |||
82 | // Gets the Transactional annotation, if existing. |
||
83 | 13 | $annotation = $this->getTransactionalAnnotation($methodDefinition); |
|
84 | |||
85 | // The transactional policy is determined by the annotation, if found. |
||
86 | // If missing, default behaviour is to do nothing (no transaction started). |
||
87 | 13 | View Code Duplication | if ($annotation === null) { |
0 ignored issues
–
show
|
|||
88 | 2 | $policy = Transactional::NOT_REQUIRED; |
|
89 | 13 | } elseif ($annotation->getPolicy() === null) { |
|
90 | 1 | $policy = $this->container->getParameter( |
|
91 | 1 | Configuration::ROOT_NODE_NAME . '.' . Configuration::DEFAULT_POLICY); |
|
92 | 2 | } else { |
|
93 | 10 | $policy = $annotation->getPolicy(); |
|
94 | } |
||
95 | |||
96 | 13 | if (($policy === Transactional::NOT_REQUIRED) && ($annotation === null)) { |
|
97 | // No annotation found: there is probably a bug in the pointcut class, because the interceptor should not |
||
98 | // have been invoked. |
||
99 | 2 | $this->logger->warning( |
|
100 | 'Transactional interceptor was invoked, but no annotation was found for method \'' . |
||
101 | 2 | $methodDefinition->getDeclaringClass()->getName() . '::' . $methodDefinition->getName() . '\'' |
|
0 ignored issues
–
show
|
|||
102 | 2 | ); |
|
103 | 2 | } |
|
104 | |||
105 | 13 | $transactionRequired = false; |
|
106 | 13 | if ($annotation !== null) { |
|
107 | // Determine if a transaction must be started. |
||
108 | 11 | $transactionRequired = $this->isTransactionRequired( |
|
109 | 11 | $policy, |
|
110 | 11 | $this->getEntityManager()->getConnection()->isTransactionActive() |
|
111 | 11 | ); |
|
112 | 11 | } |
|
113 | |||
114 | 13 | $this->beforeMethodInvocation($transactionRequired); |
|
115 | try { |
||
116 | // Invokes the method. |
||
117 | 13 | $this->logger->debug( |
|
118 | 13 | $methodDefinition->getDeclaringClass()->getName() . '::' . $methodDefinition->getName() |
|
0 ignored issues
–
show
|
|||
119 | 13 | ); |
|
120 | 13 | $result = $method->proceed(); |
|
121 | 7 | $this->afterMethodInvocationSuccess($transactionRequired); |
|
122 | 13 | } catch (Exception $e) { |
|
123 | // Manage special exceptions (commit or rollback strategy). |
||
124 | 6 | View Code Duplication | if ($annotation === null) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
125 | // At this point, it means there is no inner transaction context for the method. |
||
126 | 1 | $noRollbackExceptions = null; |
|
127 | 6 | } elseif ($annotation->getNoRollbackExceptions() === null) { |
|
128 | // No exceptions set in the annotation (even if the parameter was found), use the default configuration. |
||
129 | 3 | $noRollbackExceptions = $this->container->getParameter( |
|
130 | 3 | Configuration::ROOT_NODE_NAME . '.' . Configuration::NO_ROLLBACK_EXCEPTIONS |
|
131 | 3 | ); |
|
132 | 3 | } else { |
|
133 | // Use the annotation parameter. |
||
134 | 2 | $noRollbackExceptions = $annotation->getNoRollbackExceptions(); |
|
135 | } |
||
136 | 6 | $this->afterMethodInvocationFailure($transactionRequired, $e, $noRollbackExceptions); |
|
137 | } |
||
138 | |||
139 | 7 | return $result; |
|
0 ignored issues
–
show
The variable
$result does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Get the entity manager |
||
144 | * |
||
145 | * @return EntityManagerInterface |
||
146 | */ |
||
147 | 11 | protected function getEntityManager() |
|
148 | { |
||
149 | 11 | return $this->entityManagerRegistry->getManager(); |
|
150 | } |
||
151 | |||
152 | /** |
||
153 | * Performs additional process after the intercepted method call was performed with a failure. |
||
154 | * |
||
155 | * @param boolean $transactionRequired If a new transaction was required. |
||
156 | * @param Exception $e Exception to throw at the end of the additional process. |
||
157 | * @param string[] $noRollbackExceptions An array of exceptions that shall not lead to a transaction rollback. |
||
158 | * @throws Exception At the end of the additional process (given exception). |
||
159 | */ |
||
160 | 6 | protected function afterMethodInvocationFailure( |
|
161 | $transactionRequired, |
||
162 | Exception $e, |
||
163 | array $noRollbackExceptions = null |
||
164 | ) |
||
165 | { |
||
166 | 6 | if ($transactionRequired) { |
|
167 | 5 | if ($this->isRollbackEnabled($e, $noRollbackExceptions)) { |
|
168 | // Rollbacks the transaction. |
||
169 | 2 | $this->logger->debug('Exception ' . get_class($e) . ' causes rollback'); |
|
170 | 2 | $this->rollback(); |
|
171 | 2 | } else { |
|
172 | // Commits the transaction. |
||
173 | 3 | $this->logger->debug('No rollback for exception ' . get_class($e)); |
|
174 | 3 | $this->commit(); |
|
175 | } |
||
176 | 5 | } |
|
177 | |||
178 | 6 | throw $e; |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * Performs additional process after the intercepted method call was performed successfully. |
||
183 | * |
||
184 | * @param boolean $transactionRequired If a new transaction was required. |
||
185 | */ |
||
186 | 7 | protected function afterMethodInvocationSuccess($transactionRequired) |
|
187 | { |
||
188 | 7 | if ($transactionRequired) { |
|
189 | // Commits the transaction. |
||
190 | 4 | $this->commit(); |
|
191 | 4 | } |
|
192 | 7 | } |
|
193 | |||
194 | /** |
||
195 | * Performs additional process before the intercepted method call is performed. |
||
196 | * |
||
197 | * @param boolean $transactionRequired If a new transaction is required. |
||
198 | */ |
||
199 | 13 | protected function beforeMethodInvocation($transactionRequired) |
|
200 | { |
||
201 | 13 | if ($transactionRequired) { |
|
202 | // Starts a transaction. |
||
203 | 9 | $this->beginTransaction(); |
|
204 | 9 | } |
|
205 | 13 | } |
|
206 | |||
207 | /** |
||
208 | * Starts a transaction on the underlying database connection of a Doctrine's entity manager. |
||
209 | */ |
||
210 | 9 | protected function beginTransaction() |
|
211 | { |
||
212 | 9 | $this->logger->debug(static::class . '::beginTransaction'); |
|
213 | 9 | $this->getEntityManager()->beginTransaction(); |
|
214 | 9 | } |
|
215 | |||
216 | /** |
||
217 | * Commits the pending transaction on the underlying database connection of a Doctrine's entity manager. |
||
218 | */ |
||
219 | 7 | protected function commit() |
|
220 | { |
||
221 | 7 | $this->logger->debug(static::class . '::commit'); |
|
222 | 7 | $this->getEntityManager()->flush(); |
|
223 | 7 | $this->getEntityManager()->commit(); |
|
224 | 7 | } |
|
225 | |||
226 | /** |
||
227 | * Closes the entity manager, and rollbacks the pending transaction on the underlying database connection of a |
||
228 | * Doctrine's entity manager with the given name. This method also resets the manager, so as it can be recreated for |
||
229 | * a new transaction, when needed. |
||
230 | */ |
||
231 | 2 | protected function rollback() |
|
232 | { |
||
233 | 2 | $this->logger->debug(static::class . '::rollback'); |
|
234 | 2 | $this->getEntityManager()->rollback(); |
|
235 | |||
236 | // Close the manager if there is no transaction started. |
||
237 | 2 | if (!$this->getEntityManager()->getConnection()->isTransactionActive()) { |
|
238 | 2 | $this->getEntityManager()->close(); |
|
239 | 2 | $this->entityManagerRegistry->resetManager(); |
|
240 | 2 | } |
|
241 | 2 | } |
|
242 | |||
243 | /** |
||
244 | * Gets the Transactional annotation, if any, looking at method level as a priority, then at class level. |
||
245 | * |
||
246 | * @param ReflectionMethod $method Method definition. |
||
247 | * @return Transactional The transaction annotation, or <code>null</code> if not found. |
||
248 | */ |
||
249 | 13 | protected function getTransactionalAnnotation(ReflectionMethod $method) |
|
250 | { |
||
251 | 13 | $annotation = $this->reader->getMethodAnnotation($method, Transactional::class); |
|
252 | 13 | if ($annotation === null) { |
|
253 | // If there is no method-level annotation, gets class-level annotation. |
||
254 | 8 | $annotation = $this->reader->getClassAnnotation($method->getDeclaringClass(), Transactional::class); |
|
255 | 8 | } |
|
256 | 13 | return $annotation; |
|
257 | } |
||
258 | |||
259 | /** |
||
260 | * Tells whether a transaction must be started, depending on the configured policy and the current TX status. |
||
261 | * |
||
262 | * @param int $policy One of the policy defined in the Transactional annotation. |
||
263 | * @param boolean $isTransactionActive Whether a transaction is already active when invoking a method. |
||
264 | * @return boolean <code>true</code> if a new transaction is required, <code>false</code> otherwise. |
||
265 | */ |
||
266 | 11 | protected function isTransactionRequired($policy, $isTransactionActive) |
|
267 | { |
||
268 | 11 | return ($policy === Transactional::NESTED) || (($policy === Transactional::REQUIRED) && !$isTransactionActive); |
|
269 | } |
||
270 | |||
271 | /** |
||
272 | * Checks whether a rollback shall be executed for a given exception. |
||
273 | * |
||
274 | * @param Exception $e An exception. |
||
275 | * @param string[] $noRollbackExceptions An array of exceptions that shall not lead to a transaction rollback. |
||
276 | * @return boolean <code>true</code> if the rollback must be executed, e.g. this exception shall not be "ignored" by |
||
277 | * the current transactional context. |
||
278 | */ |
||
279 | 5 | protected function isRollbackEnabled(Exception $e, array $noRollbackExceptions = null) |
|
280 | { |
||
281 | 5 | $rollbackEnabled = true; |
|
282 | 5 | if ($noRollbackExceptions !== null) { |
|
283 | 3 | foreach ($noRollbackExceptions as $noRollbackException) { |
|
284 | 3 | if (is_a($e, $noRollbackException)) { |
|
285 | 3 | $rollbackEnabled = false; |
|
286 | 3 | break; |
|
287 | } |
||
288 | 3 | } |
|
289 | 3 | } |
|
290 | |||
291 | 5 | return $rollbackEnabled; |
|
292 | } |
||
293 | } |
||
294 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.