Completed
Push — 2.0 ( 1160ec...acba87 )
by Vermeulen
05:15
created

AbstractQuery::getExecuter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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