Skip to content

Commit b676d07

Browse files
committed
Small fixes & improvements
- Extract SelectIteratorTrait from SelectIterator - Check flattenFields more thoroughly, and remove it in some parts where it is not allowed anymore - Improve documentation with more examples
1 parent cf94c00 commit b676d07

File tree

9 files changed

+277
-74
lines changed

9 files changed

+277
-74
lines changed

README.md

Lines changed: 148 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ This would be aquivalent to this string SELECT query (when using MySQL):
186186
$selectStatement = $db->select('SELECT `fufumama`,`b`.`lalala`,`a`.`setting_value` AS "result",(`a`.`setting_value`+`b`.`blabla_value`) AS "result2" FROM `blobs`.`aa_sexy` `a`,`blobs`.`aa_blubli` `b` LEFT JOIN `blobs`.`aa_blubla` `c` ON (`c`.`field` = `b`.`field5` AND `b`.`sexy` = ?) WHERE (`a`.`field` = `b`.`field`) AND `setting_id`=? AND `boring_field_name` IN (?,?,?,?) AND (`setting_value` = ? OR `setting_value2` = ?) GROUP BY `a`.`field` ORDER BY `a`.`field` DESC LIMIT 10 OFFSET 5 FOR UPDATE', [5,'orders_xml_override',5,3,8,13,'one','two']);
187187
```
188188

189-
Important parts of how the conversion works:
189+
#### Important conversion details:
190190

191191
- If an expression contains something like :fieldname: it is assumed that it is a field or table name which will then be escaped. For simple WHERE restrictions or fields definitions field names are escaped automatically.
192192
- You can use "field" if there is just one field, or "fields" for multiple fields. The same with "table" and "tables".
@@ -497,9 +497,7 @@ DBBuilderInterface offers the following functions:
497497

498498
All except the last two return a builder object which helps you easily create a query and get the results. Compared to DBInterface you do not have to remember what data can be contained in a structured query - your IDE will suggest whatever is available. You can also have a look at the builder objects themselves - they are all very short.
499499

500-
It is almost easiest to just try it out yourself - in the background DBBuilder converts the query into a structured query to pass to DBInterface.
501-
502-
There are some examples below for each of the 6 builder functions:
500+
Looking at some examples should make the usage quite clear - here are some for each of the 6 builder functions:
503501

504502
### Count
505503

@@ -521,6 +519,42 @@ An easy way to just count the number of rows.
521519

522520
### Select
523521

522+
Select queries can become the most complex, so they have many options - here is an example with all of them (many are optional though!):
523+
524+
```php
525+
$selectQuery = $dbBuilder
526+
->select()
527+
->fields([
528+
'u.userId',
529+
'name' => 'a.firstName',
530+
])
531+
->inTables([
532+
'users u',
533+
'users_addresses a',
534+
])
535+
->where([
536+
':u.userId: = :a.userId:',
537+
'u.zipCode' => 33769,
538+
])
539+
->groupBy([
540+
'u.userId',
541+
])
542+
->orderBy([
543+
'u.createDate',
544+
])
545+
->limitTo(3)
546+
->startAt(0)
547+
->blocking();
548+
549+
foreach ($selectQuery as $result) {
550+
echo $result['userId'] . ' => ' . $result['name'];
551+
}
552+
```
553+
554+
The above query takes advantage that each SELECT query builder can be iterated over. As soon as the foreach loop starts the query is executed and one entry after the other is retrieved.
555+
556+
If you want to retrieve all results at once (because you know you need them anyway), this is another option:
557+
524558
```php
525559
$userResults = $dbBuilder
526560
->select()
@@ -546,16 +580,53 @@ $userResults = $dbBuilder
546580
->startAt(0)
547581
->blocking()
548582
->getAllEntries();
583+
584+
foreach ($userResults as $result) {
585+
echo $result['userId'] . ' => ' . $result['name'];
586+
}
549587
```
550588

551-
Select queries can become the most complex, so they have many options - above you can see all of them, except for the last call, where you have different options:
589+
Or if you only need one entry:
552590

553-
- getAllEntries, to retrieve an array with all the entries at once
554-
- getOneEntry, to just get exactly one entry
555-
- getIterator, to get an object you can iterate over (foreach) so you can get one result after the other
556-
- getFlattenedFields, which means the results are "flattened"
591+
```php
592+
$result = $dbBuilder
593+
->select()
594+
->fields([
595+
'u.userId',
596+
'name' => 'a.firstName',
597+
])
598+
->inTables([
599+
'users u',
600+
'users_addresses a',
601+
])
602+
->where([
603+
':u.userId: = :a.userId:',
604+
'u.zipCode' => 33769,
605+
])
606+
->groupBy([
607+
'u.userId',
608+
])
609+
->orderBy([
610+
'u.createDate',
611+
])
612+
->limitTo(3)
613+
->startAt(0)
614+
->blocking()
615+
->getOneEntry();
616+
617+
echo $result['userId'] . ' => ' . $result['name'];
618+
```
619+
620+
Note that you can use `field` instead of `fields` and `inTable` instead of `inTables` if you want to pass only one value (as a string), and that you can pass a string to `groupBy` and `orderBy` if you only want to use one string value.
621+
622+
There are four options to get the data from a select query builder:
557623

558-
getFlattenedFields can be useful for something like this:
624+
- **getIterator**, to get an object you can iterate over (foreach) so you can get one result after the other - this is implicitely used in the first example by putting the builder into the foreach loop, as the builder implements IteratorAggregate
625+
- **getAllEntries**, to retrieve an array with all the entries at once, which was the second example
626+
- **getOneEntry**, to just get exactly one entry, used in the third example
627+
- **getFlattenedFields**, which means the results are "flattened"
628+
629+
`getFlattenedFields` can be useful for something like this:
559630

560631
```php
561632
$userIds = $dbBuilder
@@ -566,14 +637,18 @@ $userIds = $dbBuilder
566637
'u.zipCode' => 33769,
567638
])
568639
->getFlattenedFields();
640+
641+
foreach ($userIds as $userId) {
642+
// Do something which each $userId here
643+
}
569644
```
570645

571646
Instead of a list of arrays each with a field "userId", the results are flattened and directly return a list of user IDs. Flattening is mostly useful for IDs or other simple lists of values, where you just need one array instead of an array of an array.
572647

573648
### Insert
574649

575650
```php
576-
$newUserId = $dbBuilder
651+
$newUserIdFromDatabase = $dbBuilder
577652
->insert()
578653
->inTable('users')
579654
->set([
@@ -603,7 +678,7 @@ $rowsAffected = $dbBuilder
603678
->writeAndReturnAffectedNumber();
604679
```
605680

606-
You can use `writeAndReturnAffectedNumber` if you are interested in the number of affected/changed rows, or `write` if you do not need that information. Another optionsnot shown above is `orderBy`.
681+
You can use `writeAndReturnAffectedNumber` if you are interested in the number of affected/changed rows, or `write` if you do not need that information. Another option which is not shown above is `orderBy`.
607682

608683
### Insert or Update
609684

@@ -647,6 +722,67 @@ You can use `writeAndReturnAffectedNumber` if you are interested in the number o
647722

648723
The transaction function works the same as the one in DBInterface - in fact, DBBuilderInterface just passes it as-is to DBInterface.
649724

725+
### General syntax rules
726+
727+
For simple column names to values within any queries, you can just use the name to value syntax like you do in PHP:
728+
729+
```php
730+
$user = $dbBuilder
731+
->select()
732+
->inTable('users')
733+
->where([
734+
'user_id' => $userId, // user_id must be equal to $userId
735+
])
736+
->getOneEntry();
737+
738+
// $user now contains all table column and values:
739+
echo $user['user_id'];
740+
```
741+
742+
The values are separated from the query to ensure safety, and the table names and column names are quoted for you.
743+
744+
If you want to use more complex expressions, you are free to do so:
745+
746+
```php
747+
$user = $dbBuilder
748+
->select()
749+
->inTable('users')
750+
->where([
751+
':user_id: BETWEEN ? AND ?' => [15, 55],
752+
':create_date: > ?' => time() - 86400,
753+
])
754+
->getOneEntry();
755+
```
756+
757+
In these cases make sure to surround all table column names / field names and table names with colons, so the library can escape them. You can use any SQL syntax you want, and each entry in a WHERE clause is connected by AND - so the WHERE part is converted to the following by the library:
758+
759+
```sql
760+
... WHERE (`user_id` BETWEEN ? AND ?) AND (`create_date` > ?) ...
761+
```
762+
763+
For custom expressions every expression is surrounded by brackets, to make sure they do not influence each other, and the parameters are sent separately from the query, in this case: `[15, 55, time() - 86400]`
764+
765+
This syntax is used consistently for any data passed to the library, and where that type of syntax can be translated to valid SQL. So an UPDATE query could look like this:
766+
767+
```php
768+
$rowsAffected = $dbBuilder
769+
->update()
770+
->inTable('users')
771+
->set([
772+
'last_login_date' => time(),
773+
':visits: = :visits: + 1',
774+
':full_name: = CONCAT(:first_name:,:last_name:)',
775+
':balance: = :balance: + ?' => $balanceIncrease,
776+
])
777+
->where([
778+
'user_id' => 33,
779+
':last_login_date: < ?' => time()-86400,
780+
])
781+
->writeAndReturnAffectedNumber();
782+
```
783+
784+
This should make it easy to read and write queries, even if you don't know much SQL, and you don't have to think about separating the query and the parameters yourself - the library is doing it for you.
785+
650786
Guidelines to use this library
651787
------------------------------
652788

captainhook.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"conditions": []
3636
},
3737
{
38-
"action": "vendor/bin/phpcs --standard=ruleset.xml --extensions=php src tests",
38+
"action": "vendor/bin/phpcs --standard=ruleset.xml --extensions=php --cache src tests",
3939
"options": [],
4040
"conditions": []
4141
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"scripts": {
5353
"phpstan": "vendor/bin/phpstan analyse src --level=7",
5454
"phpunit": "vendor/bin/phpunit --colors=always",
55-
"phpcs": "vendor/bin/phpcs --standard=ruleset.xml --extensions=php src tests",
55+
"phpcs": "vendor/bin/phpcs --standard=ruleset.xml --extensions=php --cache src tests",
5656
"codecoverage": "vendor/bin/phpunit --coverage-html tests/_reports"
5757
}
5858
}

ruleset.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<rule ref="SlevomatCodingStandard.ControlStructures.AssignmentInCondition"/>
1111
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowContinueWithoutIntegerOperandInSwitch"/>
1212
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowEmpty"/>
13-
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements"/>
1413
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
1514
<properties>
1615
<property name="searchAnnotations" value="true"/>

src/Builder/SelectIterator.php

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,76 +10,26 @@
1010
*/
1111
class SelectIterator implements \Iterator
1212
{
13-
/**
14-
* @var DBInterface
15-
*/
16-
private $db;
13+
use SelectIteratorTrait;
1714

1815
/**
19-
* @var array SELECT query to execute
16+
* @var DBInterface
2017
*/
21-
private $query = [];
18+
private $source;
2219

2320
/**
2421
* @var DBSelectQueryInterface|null
2522
*/
2623
private $selectReference;
2724

28-
/**
29-
* @var int
30-
*/
31-
private $position = -1;
32-
3325
/**
3426
* @var array|null
3527
*/
3628
private $lastResult;
3729

3830
public function __construct(DBInterface $db, array $query)
3931
{
40-
$this->db = $db;
32+
$this->source = $db;
4133
$this->query = $query;
4234
}
43-
44-
public function current()
45-
{
46-
return $this->lastResult;
47-
}
48-
49-
public function next()
50-
{
51-
if (isset($this->selectReference)) {
52-
$this->lastResult = $this->db->fetch($this->selectReference);
53-
$this->position++;
54-
}
55-
}
56-
57-
public function key()
58-
{
59-
return $this->position;
60-
}
61-
62-
public function valid()
63-
{
64-
return ( $this->lastResult === null ? false : true );
65-
}
66-
67-
public function rewind()
68-
{
69-
$this->clear();
70-
71-
$this->selectReference = $this->db->select($this->query);
72-
73-
$this->next();
74-
}
75-
76-
public function clear()
77-
{
78-
if (isset($this->selectReference)) {
79-
$this->db->clear($this->selectReference);
80-
}
81-
$this->position = -1;
82-
$this->selectReference = null;
83-
$this->lastResult = null;
84-
}
8535
}

0 commit comments

Comments
 (0)