AbstractQuery::assembleRequestPart()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 8
nop 2
dl 0
loc 28
rs 9.5222
c 0
b 0
f 0
1
<?php
2
3
namespace BfwSql\Queries;
4
5
use \Exception;
6
7
/**
8
 * Abstract class used for all query writer class.
9
 * 
10
 * @package bfw-sql
11
 * @author Vermeulen Maxime <[email protected]>
12
 * @version 2.0
13
 * 
14
 * @method \BfwSql\Queries\AbstractQuery table(string|array $nameInfos, string|array|null $columns=null)
15
 * @method \BfwSql\Queries\AbstractQuery where(string $expr, array|null $preparedParams = null)
16
 */
17
abstract class AbstractQuery
18
{
19
    /**
20
     * @const ERR_ASSEMBLE_MISSING_TABLE_NAME Exception code if the user
21
     * try to generate a request without table name.
22
     */
23
    const ERR_ASSEMBLE_MISSING_TABLE_NAME = 2401001;
24
    
25
    /**
26
     * @const ERR_CALL_UNKNOWN_METHOD Exception code if the __call method
27
     * receive a method which is really unknown
28
     */
29
    const ERR_CALL_UNKNOWN_METHOD = 2401002;
30
    
31
    /**
32
     * @const ERR_ASSEMBLE_EMPTY_PART Exception code if during the query
33
     * assemble, a part is empty but should not be empty.
34
     */
35
    const ERR_ASSEMBLE_EMPTY_PART = 2401003;
36
    
37
    /**
38
     * @var \BfwSql\SqlConnect $sqlConnect SqlConnect object
39
     */
40
    protected $sqlConnect;
41
    
42
    /**
43
     * @var \BfwSql\Executers\Common $executer
44
     */
45
    protected $executer;
46
    
47
    /**
48
     * @var string $assembledRequest The request will be executed
49
     */
50
    protected $assembledRequest = '';
51
    
52
    /**
53
     * @var string[] $preparedParams Arguments used by prepared request
54
     */
55
    protected $preparedParams = [];
56
    
57
    /**
58
     * @var array $queriesParts All parts used to generate the final request
59
     */
60
    protected $queriesParts = [];
61
    
62
    /**
63
     * @var string $requestType The type of the request (ex: "select")
64
     */
65
    protected $requestType = '';
66
    
67
    /**
68
     * @var \BfwSql\Queries\SGBD\AbstractSGBD $querySgbd The QueriesSGBD
69
     * instance which define query part allowed per SGBD or change some
70
     * query part generation per SGBD.
71
     */
72
    protected $querySgbd;
73
    
74
    /**
75
     * Constructor
76
     * 
77
     * @param \BfwSql\SqlConnect $sqlConnect Instance of SGBD connexion
78
     */
79
    public function __construct(\BfwSql\SqlConnect $sqlConnect)
80
    {
81
        $this->sqlConnect = $sqlConnect;
82
        
83
        $usedClass      = \BfwSql\UsedClass::getInstance();
84
        $executerClass  = $usedClass->obtainClassNameToUse('ExecutersCommon');
85
        $this->executer = new $executerClass($this);
86
        
87
        $sgbdType        = $this->sqlConnect->getType();
88
        $querySgbdClass  = $usedClass->obtainClassNameToUse(
89
            'QueriesSgbd'.ucfirst($sgbdType)
90
        );
91
        $this->querySgbd = new $querySgbdClass($this);
92
        
93
        $this->defineQueriesParts();
94
    }
95
    
96
    /**
97
     * Define all part which will be necessary to generate the final request
98
     * 
99
     * @return void
100
     */
101
    protected function defineQueriesParts()
102
    {
103
        $usedClass  = \BfwSql\UsedClass::getInstance();
104
        $tableClass = $usedClass->obtainClassNameToUse('QueriesPartsTable');
105
        $whereClass = $usedClass->obtainClassNameToUse('QueriesPartsWhereList');
106
        
107
        $this->queriesParts = [
108
            'table' => new $tableClass($this),
109
            'where' => new $whereClass($this)
110
        ];
111
        
112
        $this->querySgbd->disableQueriesParts($this->queriesParts);
113
    }
114
    
115
    /**
116
     * Magic method __call, used when the user call a non-existing method
117
     * @link http://php.net/manual/en/language.oop5.overloading.php#object.call
118
     * 
119
     * @param string $name the name of the method being called
120
     * @param array $args an enumerated array containing the parameters
121
     *  passed to the $name'ed method.
122
     * 
123
     * @return object The current instance if the part object have the method
124
     * __invoke(), or directly the part object asked
125
     * 
126
     * @throws Exception If the part not exist
127
     */
128
    public function __call(string $name, array $args)
129
    {
130
        if (!isset($this->queriesParts[$name])) {
131
            throw new Exception(
132
                'Unknown called method '.$name,
133
                self::ERR_CALL_UNKNOWN_METHOD
134
            );
135
        }
136
        
137
        if (!method_exists($this->queriesParts[$name], '__invoke')) {
138
            return $this->queriesParts[$name];
139
        }
140
        
141
        $this->queriesParts[$name](...$args);
142
        return $this;
143
    }
144
    
145
    /**
146
     * Getter to access at sqlConnect property
147
     * 
148
     * @return \BfwSql\SqlConnect
149
     */
150
    public function getSqlConnect(): \BfwSql\SqlConnect
151
    {
152
        return $this->sqlConnect;
153
    }
154
    
155
    /**
156
     * Getter accessor to property executer
157
     * 
158
     * @return \BfwSql\Executers\Common
159
     */
160
    public function getExecuter(): \BfwSql\Executers\Common
161
    {
162
        return $this->executer;
163
    }
164
    
165
    /**
166
     * Getter accessor to property queriesParts
167
     * 
168
     * @return array
169
     */
170
    public function getQueriesParts(): array
171
    {
172
        return $this->queriesParts;
173
    }
174
    
175
    /**
176
     * Getter to access to assembledRequest property
177
     * 
178
     * @return string
179
     */
180
    public function getAssembledRequest(): string
181
    {
182
        return $this->assembledRequest;
183
    }
184
    
185
    /**
186
     * Getter to access at preparedParams property
187
     * 
188
     * @return array
189
     */
190
    public function getPreparedParams(): array
191
    {
192
        return $this->preparedParams;
193
    }
194
    
195
    /**
196
     * Getter to access at requestType property
197
     * 
198
     * @return string
199
     */
200
    public function getRequestType(): string
201
    {
202
        return $this->requestType;
203
    }
204
    
205
    /**
206
     * Getter to access at querySgbd property
207
     * 
208
     * @return \BfwSql\Queries\SGBD\AbstractSGBD
209
     */
210
    public function getQuerySgbd(): \BfwSql\Queries\SGBD\AbstractSGBD
211
    {
212
        return $this->querySgbd;
213
    }
214
    
215
    /**
216
     * Check if a request is assemble or not.
217
     * If not, run the method assembleRequest.
218
     * 
219
     * @return boolean
220
     */
221
    public function isAssembled(): bool
222
    {
223
        if ($this->assembledRequest === '') {
224
            return false;
225
        }
226
        
227
        return true;
228
    }
229
    
230
    /**
231
     * Write the query
232
     * 
233
     * @return void
234
     */
235
    protected function assembleRequest()
236
    {
237
        if (empty($this->queriesParts['table']->getName())) {
238
            throw new Exception(
239
                'The main table of the request should be declared.',
240
                self::ERR_ASSEMBLE_MISSING_TABLE_NAME
241
            );
242
        }
243
        
244
        $generateOrder = $this->obtainGenerateOrder();
245
        
246
        $this->assembledRequest = '';
247
        
248
        foreach ($generateOrder as $partName => $partInfos) {
249
            $requestPart = $this->assembleRequestPart(
250
                $partName,
251
                $partInfos
252
            );
253
            
254
            if ($requestPart !== '') {
255
                //To avoid many blank lines into generated request.
256
                $this->assembledRequest .= $requestPart."\n";
257
            }
258
        }
259
    }
260
    
261
    /**
262
     * Define the order to call all queries part.
263
     * Each item contain an array with somes key to define some how generate
264
     * the query part.
265
     * Properties is:
266
     * * callback: The callback to call to generate the query part
267
     * * canBeEmpty : If the part can be empty, or not
268
     * * prefix : The prefix to add before the generated sql
269
     * * usePartPrefix : If the prefix should be added before the generated sql
270
     * 
271
     * @return array
272
     */
273
    protected abstract function obtainGenerateOrder(): array;
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
274
    
275
    /**
276
     * Generate the sql query for a part
277
     * 
278
     * @param string $partName The part name
279
     * @param array $partInfos Infos about the generation of the sql part
280
     * 
281
     * @return string
282
     * 
283
     * @throws \Exception If the sql generated is empty but should not be
284
     */
285
    protected function assembleRequestPart(
286
        string $partName,
287
        array $partInfos
288
    ): string {
289
        $this->addMissingKeysToPartInfos($partName, $partInfos);
290
        
291
        $generateSqlPart = '';
292
293
        if (isset($partInfos['callback'])) {
294
            $generateSqlPart = $partInfos['callback']();
295
        }
296
        
297
        if (empty($generateSqlPart)) {
298
            if ($partInfos['canBeEmpty'] === false) {
299
                throw new Exception(
300
                    'The part '.$partName.' should not be empty.',
301
                    self::ERR_ASSEMBLE_EMPTY_PART
302
                );
303
            }
304
            
305
            return '';
306
        }
307
        
308
        if ($partInfos['usePartPrefix'] === true) {
309
            return $partInfos['prefix'].' '.$generateSqlPart;
310
        }
311
        
312
        return $generateSqlPart;
313
    }
314
    
315
    /**
316
     * Check each item of the array containing generation infos for a sql part
317
     * and add missing keys
318
     * 
319
     * @param string $partName The part name
320
     * @param array &$partInfos Infos about the generation of the sql part
321
     * 
322
     * @return void
323
     */
324
    protected function addMissingKeysToPartInfos(
325
        string $partName,
326
        array &$partInfos
327
    ) {
328
        $defaultValues = $this->obtainPartInfosDefaultValues($partName);
329
        
330
        if (!isset($partInfos['callback'])) {
331
            $partInfos['callback'] = [$defaultValues, 'generate'];
332
        }
333
        
334
        if (!isset($partInfos['prefix'])) {
335
            $partInfos['prefix'] = $defaultValues->getPartPrefix();
336
        }
337
        
338
        if (!isset($partInfos['usePartPrefix'])) {
339
            $partInfos['usePartPrefix'] = $defaultValues->getUsePartPrefix();
340
        }
341
        
342
        if (!isset($partInfos['canBeEmpty'])) {
343
            $partInfos['canBeEmpty'] = $defaultValues->getCanBeEmpty();
344
        }
345
    }
346
    
347
    /**
348
     * Return the object containing all default value for a part infos.
349
     * Used by the method who add missing key into the part infos array.
350
     * If the part not exist into the array queriesParts, use an anonymous
351
     * class who extend AbstractPart to have all method and property with
352
     * their default values.
353
     * 
354
     * @param string $partName The part name
355
     * 
356
     * @return \BfwSql\Queries\Parts\AbstractPart
357
     */
358
    protected function obtainPartInfosDefaultValues(
359
        string $partName
360
    ): Parts\AbstractPart {
361
        if (
362
            isset($this->queriesParts[$partName]) &&
363
            $this->queriesParts[$partName] instanceof Parts\AbstractPart
364
        ) {
365
            return $this->queriesParts[$partName];
366
        }
367
        
368
        return new class($this) extends Parts\AbstractPart {
369
            public function generate(): string
370
            {
371
                return '';
372
            }
373
        };
374
    }
375
    
376
    /**
377
     * Return the assembled request
378
     * 
379
     * @param boolean $force : Force to re-assemble request
380
     * 
381
     * @return string
382
     */
383
    public function assemble(bool $force = false): string
384
    {
385
        if ($this->isAssembled() === false || $force === true) {
386
            $this->assembleRequest();
387
        }
388
        
389
        return $this->assembledRequest;
390
    }
391
    
392
    /**
393
     * Execute the assembled request
394
     * 
395
     * @throws \Exception If the request fail
396
     * 
397
     * @return \PDOStatement|integer
398
     */
399
    public function execute()
400
    {
401
        return $this->executer->execute();
402
    }
403
    
404
    /**
405
     * To call this own request without use query writer
406
     * 
407
     * @param string $request The user request
408
     * 
409
     * @return $this
410
     */
411
    public function query(string $request): self
412
    {
413
        $this->assembledRequest = $request;
414
        
415
        return $this;
416
    }
417
    
418
    /**
419
     * Add filters to prepared requests
420
     * 
421
     * @param array $preparedParams Filters to add in prepared request
422
     * 
423
     * @return $this
424
     */
425
    public function addPreparedParams(array $preparedParams): self
426
    {
427
        foreach ($preparedParams as $prepareKey => $prepareValue) {
428
            $this->preparedParams[$prepareKey] = $prepareValue;
429
        }
430
        
431
        return $this;
432
    }
433
}
434