Completed
Pull Request — 2.0 (#62)
by
unknown
05:20
created

ModelLayer::setDeferrable()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 45
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
c 4
b 1
f 1
dl 0
loc 45
rs 8.5806
cc 4
eloc 26
nc 4
nop 2
1
<?php
2
/*
3
 * This file is part of the PommProject/ModelManager package.
4
 *
5
 * (c) 2014 - 2015 Grégoire HUBERT <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PommProject\ModelManager\ModelLayer;
11
12
use PommProject\Foundation\Client\Client;
13
use PommProject\Foundation\Client\ClientInterface;
14
use PommProject\Foundation\Session\Connection;
15
use PommProject\Foundation\Session\ResultHandler;
16
use PommProject\ModelManager\Exception\ModelLayerException;
17
use PommProject\ModelManager\Model\Model;
18
19
/**
20
 * ModelLayer
21
 *
22
 * ModelLayer handles mechanisms around model method calls (transactions,
23
 * events etc.).
24
 *
25
 * @package     ModelManager
26
 * @copyright   2014 - 2015 Grégoire HUBERT
27
 * @author      Grégoire HUBERT
28
 * @license     X11 {@link http://opensource.org/licenses/mit-license.php}
29
 * @see         Client
30
 */
31
abstract class ModelLayer extends Client
32
{
33
    /**
34
     * getClientType
35
     *
36
     * @see ClientInterface
37
     */
38
    public function getClientType()
39
    {
40
        return 'model_layer';
41
    }
42
43
    /**
44
     * getClientIdentifier
45
     *
46
     * @see ClientInterface
47
     */
48
    public function getClientIdentifier()
49
    {
50
        return get_class($this);
51
    }
52
53
    /**
54
     * shutdown
55
     *
56
     * @see ClientInterface
57
     */
58
    public function shutdown()
59
    {
60
    }
61
62
    /**
63
     * startTransaction
64
     *
65
     * Start a new transaction.
66
     *
67
     * @access protected
68
     * @return ModelLayer $this
69
     */
70
    protected function startTransaction()
71
    {
72
        $this->executeAnonymousQuery('begin transaction');
73
74
        return $this;
75
    }
76
77
    /**
78
     * setDeferrable
79
     *
80
     * Set given constraints to deferred/immediate in the current transaction.
81
     * This applies to constraints being deferrable or deferred by default.
82
     * If the keys is an empty arrays, ALL keys will be set at the given state.
83
     * @see http://www.postgresql.org/docs/9.0/static/sql-set-constraints.html
84
     *
85
     * @access protected
86
     * @param  array      $keys
87
     * @param  string     $state
88
     * @throws  ModelLayerException if not valid state
89
     * @return ModelLayer $this
90
     */
91
    protected function setDeferrable(array $keys, $state)
92
    {
93
        if (count($keys) === 0) {
94
            $string = 'ALL';
95
        } else {
96
            $string = join(
97
                ', ',
98
                array_map(
99
                    function ($key) {
100
                        $parts = explode('.', $key);
101
                        $escaped_parts = [];
102
103
                        foreach ($parts as $part) {
104
                            $escaped_parts[] = $this->escapeIdentifier($part);
105
                        }
106
107
                        return join('.', $escaped_parts);
108
                    },
109
                    $keys
110
                )
111
            );
112
        }
113
114
        if (!in_array($state, [ Connection::CONSTRAINTS_DEFERRED, Connection::CONSTRAINTS_IMMEDIATE ])) {
115
            throw new ModelLayerException(
116
                sprintf(<<<EOMSG
117
'%s' is not a valid constraint modifier.
118
Use Connection::CONSTRAINTS_DEFERRED or Connection::CONSTRAINTS_IMMEDIATE.
119
EOMSG
120
,
121
                    $state
122
                )
123
            );
124
        }
125
126
        $this->executeAnonymousQuery(
127
            sprintf(
128
                "set constraints %s %s",
129
                $string,
130
                $state
131
            )
132
        );
133
134
        return $this;
135
    }
136
137
    /**
138
     * setTransactionIsolationLevel
139
     *
140
     * Transaction isolation level tells PostgreSQL how to manage with the
141
     * current transaction. The default is "READ COMMITTED".
142
     * @see http://www.postgresql.org/docs/9.0/static/sql-set-transaction.html
143
     *
144
     * @access protected
145
     * @param   string     $isolation_level
146
     * @throws  ModelLayerException if not valid isolation level
147
     * @return  ModelLayer $this
148
     */
149 View Code Duplication
    protected function setTransactionIsolationLevel($isolation_level)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
150
    {
151
        $valid_isolation_levels =
152
            [
153
                Connection::ISOLATION_READ_COMMITTED,
154
                Connection::ISOLATION_REPEATABLE_READ,
155
                Connection::ISOLATION_SERIALIZABLE
156
            ];
157
158
        if (!in_array(
159
            $isolation_level,
160
            $valid_isolation_levels
161
        )) {
162
            throw new ModelLayerException(
163
                sprintf(
164
                    "'%s' is not a valid transaction isolation level. Valid isolation levels are {%s} see Connection class constants.",
165
                    $isolation_level,
166
                    join(', ', $valid_isolation_levels)
167
                )
168
            );
169
        }
170
171
        return $this->sendParameter(
172
            "set transaction isolation level %s",
173
            $isolation_level
174
        );
175
    }
176
177
    /**
178
     * setTransactionAccessMode
179
     *
180
     * Transaction access modes tell PostgreSQL if transaction are able to
181
     * write or read only.
182
     * @see http://www.postgresql.org/docs/9.0/static/sql-set-transaction.html
183
     *
184
     * @access protected
185
     * @param   string     $access_mode
186
     * @throws  ModelLayerException if not valid access mode
187
     * @return  ModelLayer $this
188
     */
189 View Code Duplication
    protected function setTransactionAccessMode($access_mode)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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.

Loading history...
190
    {
191
        $valid_access_modes =
192
            [
193
                Connection::ACCESS_MODE_READ_ONLY,
194
                Connection::ACCESS_MODE_READ_WRITE
195
            ];
196
197
        if (!in_array(
198
            $access_mode,
199
            $valid_access_modes
200
        )) {
201
            throw new ModelLayerException(
202
                sprintf(
203
                    "'%s' is not a valid transaction access mode. Valid access modes are {%s}, see Connection class constants.",
204
                    $access_mode,
205
                    join(', ', $valid_access_modes)
206
                )
207
            );
208
        }
209
210
        return $this->sendParameter(
211
            "set transaction %s",
212
            $access_mode
213
        );
214
    }
215
216
    /**
217
     * setSavePoint
218
     *
219
     * Set a savepoint in a transaction.
220
     *
221
     * @access protected
222
     * @param  string     $name
223
     * @return ModelLayer $this
224
     */
225
    protected function setSavepoint($name)
226
    {
227
        return $this->sendParameter(
228
            "savepoint %s",
229
            $this->escapeIdentifier($name)
230
        );
231
    }
232
233
    /**
234
     * releaseSavepoint
235
     *
236
     * Drop a savepoint.
237
     *
238
     * @access protected
239
     * @param  string     $name
240
     * @return ModelLayer $this
241
     */
242
    protected function releaseSavepoint($name)
243
    {
244
        return $this->sendParameter(
245
            "release savepoint %s",
246
            $this->escapeIdentifier($name)
247
        );
248
    }
249
250
    /**
251
     * rollbackTransaction
252
     *
253
     * Rollback a transaction. If a name is specified, the transaction is
254
     * rollback to the given savepoint. Otherwise, the whole transaction is
255
     * rollback.
256
     *
257
     * @access protected
258
     * @param  string|null $name
259
     * @return ModelLayer  $this
260
     */
261
    protected function rollbackTransaction($name = null)
262
    {
263
        $sql = "rollback transaction";
264
        if ($name !== null) {
265
            $sql = sprintf("rollback to savepoint %s", $this->escapeIdentifier($name));
266
        }
267
268
        $this->executeAnonymousQuery($sql);
269
270
        return $this;
271
    }
272
273
    /**
274
     * commitTransaction
275
     *
276
     * Commit a transaction.
277
     *
278
     * @access protected
279
     * @return ModelLayer $this
280
     */
281
    protected function commitTransaction()
282
    {
283
        $this->executeAnonymousQuery('commit transaction');
284
285
        return $this;
286
    }
287
288
    /**
289
     * isInTransaction
290
     *
291
     * Tell if a transaction is open or not.
292
     *
293
     * @see    Cient
294
     * @access protected
295
     * @return bool
296
     */
297
    protected function isInTransaction()
298
    {
299
        $status = $this
300
            ->getSession()
301
            ->getConnection()
302
            ->getTransactionStatus()
303
            ;
304
305
        return (bool) ($status === \PGSQL_TRANSACTION_INTRANS || $status === \PGSQL_TRANSACTION_INERROR || $status === \PGSQL_TRANSACTION_ACTIVE);
306
    }
307
308
    /**
309
     * isTransactionOk
310
     *
311
     * In PostgreSQL, an error during a transaction cancels all the queries and
312
     * rollback the transaction on commit. This method returns the current
313
     * transaction's status. If no transactions are open, it returns null.
314
     *
315
     * @access public
316
     * @return bool|null
317
     */
318
    protected function isTransactionOk()
319
    {
320
        if (!$this->isInTransaction()) {
321
            return null;
322
        }
323
324
        $status = $this
325
            ->getSession()
326
            ->getConnection()
327
            ->getTransactionStatus()
328
            ;
329
330
        return (bool) ($status === \PGSQL_TRANSACTION_INTRANS);
331
    }
332
333
    /**
334
     * sendNotify
335
     *
336
     * Send a NOTIFY event to the database server. An optional data can be sent
337
     * with the notification.
338
     *
339
     * @access protected
340
     * @param  string     $channel
341
     * @param  string     $data
342
     * @return ModelLayer $this
343
     */
344
    protected function sendNotify($channel, $data = '')
345
    {
346
        return $this->sendParameter(
347
            'notify %s, %s',
348
            $channel,
349
            $this->escapeLiteral($data)
350
        );
351
    }
352
353
    /**
354
     * executeAnonymousQuery
355
     *
356
     * Proxy to Connection::executeAnonymousQuery()
357
     *
358
     * @access protected
359
     * @param  string        $sql
360
     * @return ResultHandler
361
     */
362
    protected function executeAnonymousQuery($sql)
363
    {
364
        return $this
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getSession()->get...teAnonymousQuery($sql); of type PommProject\Foundation\Session\ResultHandler|array adds the type array to the return on line 364 which is incompatible with the return type documented by PommProject\ModelManager...::executeAnonymousQuery of type PommProject\Foundation\Session\ResultHandler.
Loading history...
365
            ->getSession()
366
            ->getConnection()
367
            ->executeAnonymousQuery($sql)
368
            ;
369
    }
370
371
    /**
372
     * escapeIdentifier
373
     *
374
     * Proxy to Connection::escapeIdentifier()
375
     *
376
     * @access protected
377
     * @param  string $string
378
     * @return string
379
     */
380
    protected function escapeIdentifier($string)
381
    {
382
        return $this
383
            ->getSession()
384
            ->getConnection()
385
            ->escapeIdentifier($string)
386
            ;
387
    }
388
389
    /**
390
     * escapeLiteral
391
     *
392
     * Proxy to Connection::escapeLiteral()
393
     *
394
     * @access protected
395
     * @param  string $string
396
     * @return string
397
     */
398
    protected function escapeLiteral($string)
399
    {
400
        return $this
401
            ->getSession()
402
            ->getConnection()
403
            ->escapeLiteral($string)
404
            ;
405
    }
406
407
    /**
408
     * getModel
409
     *
410
     * Proxy to Session::getModel();
411
     *
412
     * @access protected
413
     * @param  string    model identifier
414
     * @return Model
415
     */
416
    protected function getModel($identifier)
417
    {
418
        return $this
419
            ->getSession()
420
            ->getClientUsingPooler('model', $identifier);
421
    }
422
423
    /**
424
     * sendParameter
425
     *
426
     * Send a parameter to the server.
427
     * The parameter MUST have been properly checked and escaped if needed as
428
     * it is going to be passed AS IS to the server. Sending untrusted
429
     * parameters may lead to potential SQL injection.
430
     *
431
     * @access private
432
     * @param  string     $sql
433
     * @param  string     $identifier
434
     * @param  string     $parameter
435
     * @return ModelLayer $this
436
     */
437
    private function sendParameter($sql, $identifier, $parameter = null)
438
    {
439
        $this
440
            ->executeAnonymousQuery(
441
                sprintf(
442
                    $sql,
443
                    $identifier,
444
                    $parameter
445
                )
446
            );
447
448
        return $this;
449
    }
450
}
451