Skip to content

Commit cc61557

Browse files
committed
Add better flattened fields implementation
- Add BuilderInterface for all builder classes, moved this from the entities package - Add flattened fields trait from entities package to use the same logic about type casting here - Usages of the library should not break in any way, but because flattened fields are now more strict this will be a new "breaking" version
1 parent 23b3f19 commit cc61557

31 files changed

+838
-130
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
}
2020
],
2121
"require": {
22-
"php": "^7.4",
22+
"php": ">=7.4",
2323
"ext-pdo": "*",
2424
"squirrelphp/debug": "^0.5",
2525
"doctrine/dbal": "^2.5"

docker/compose-test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ services:
4040
POSTGRES_PASSWORD: 'password'
4141

4242
squirrel_queries_mysql:
43-
image: mysql/mysql-server:8.0
43+
image: mysql/mysql-server:latest
4444
container_name: squirrel_queries_mysql
4545
command: --default-authentication-plugin=mysql_native_password
4646
environment:

docker/test

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@
22
# Get directory of this script
33
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
44

5+
# Remove all running docker containers
6+
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." down -v --remove-orphans
7+
58
# Test SQLite and real live tests with PostgreSQL and MySQL
6-
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." up --build --force-recreate --renew-anon-volumes squirrel_queries_74
9+
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." up --build --force-recreate --renew-anon-volumes squirrel_queries_74
10+
11+
# Remove all running docker containers
12+
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." down -v

docker/test-pull

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
44

55
# Remove all running docker containers
6-
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." down -v
6+
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." down -v --remove-orphans
77

88
# Pull new docker images in case there were updates
99
docker-compose -f "$DIR/compose-test.yml" --project-directory "$DIR/.." pull

phpstan-baseline.neon

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,22 @@
11
parameters:
2-
ignoreErrors:
2+
ignoreErrors:
3+
-
4+
message: "#^Method Squirrel\\\\Queries\\\\Builder\\\\SelectEntries\\:\\:getFlattenedIntegerFields\\(\\) should return array\\<int\\> but returns array\\<bool\\|float\\|int\\|string\\|null\\>\\.$#"
5+
count: 1
6+
path: src/Builder/SelectEntries.php
7+
8+
-
9+
message: "#^Method Squirrel\\\\Queries\\\\Builder\\\\SelectEntries\\:\\:getFlattenedFloatFields\\(\\) should return array\\<float\\> but returns array\\<bool\\|float\\|int\\|string\\|null\\>\\.$#"
10+
count: 1
11+
path: src/Builder/SelectEntries.php
12+
13+
-
14+
message: "#^Method Squirrel\\\\Queries\\\\Builder\\\\SelectEntries\\:\\:getFlattenedStringFields\\(\\) should return array\\<string\\> but returns array\\<bool\\|float\\|int\\|string\\|null\\>\\.$#"
15+
count: 1
16+
path: src/Builder/SelectEntries.php
17+
18+
-
19+
message: "#^Method Squirrel\\\\Queries\\\\Builder\\\\SelectEntries\\:\\:getFlattenedBooleanFields\\(\\) should return array\\<bool\\> but returns array\\<bool\\|float\\|int\\|string\\|null\\>\\.$#"
20+
count: 1
21+
path: src/Builder/SelectEntries.php
22+

psalm-baseline.xml

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="3.11.2@d470903722cfcbc1cd04744c5491d3e6d13ec3d9">
2+
<files psalm-version="3.14.2@3538fe1955d47f6ee926c0769d71af6db08aa488">
3+
<file src="src/Builder/FlattenedFieldsWithTypeTrait.php">
4+
<InvalidReturnType occurrences="4">
5+
<code>int[]</code>
6+
<code>float[]</code>
7+
<code>string[]</code>
8+
<code>bool[]</code>
9+
</InvalidReturnType>
10+
</file>
311
<file src="src/Builder/SelectIteratorTrait.php">
412
<ImplementedReturnTypeMismatch occurrences="1">
513
<code>int</code>

ruleset.xml

+19
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,23 @@
3737
</properties>
3838
</rule>
3939
<rule ref="SlevomatCodingStandard.Commenting.EmptyComment"/>
40+
<rule ref="SlevomatCodingStandard.Arrays.DisallowImplicitArrayCreation"/>
41+
<rule ref="SlevomatCodingStandard.Functions.StrictCall"/>
42+
<rule ref="SlevomatCodingStandard.Operators.DisallowEqualOperators"/>
43+
<rule ref="SlevomatCodingStandard.PHP.DisallowReference"/>
44+
<rule ref="SlevomatCodingStandard.PHP.UselessSemicolon"/>
45+
<rule ref="SlevomatCodingStandard.ControlStructures.NewWithParentheses"/>
46+
<rule ref="SlevomatCodingStandard.Functions.DisallowEmptyFunction"/>
47+
<rule ref="SlevomatCodingStandard.Functions.TrailingCommaInCall"/>
48+
<rule ref="SlevomatCodingStandard.TypeHints.LongTypeHints"/>
49+
<rule ref="SlevomatCodingStandard.PHP.ShortList"/>
50+
<rule ref="SlevomatCodingStandard.PHP.TypeCast"/>
51+
<rule ref="SlevomatCodingStandard.Classes.ClassConstantVisibility"/>
52+
<rule ref="SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue"/>
53+
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHintSpacing"/>
54+
<rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHintSpacing"/>
55+
<rule ref="SlevomatCodingStandard.Namespaces.MultipleUsesPerLine"/>
56+
<rule ref="SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash"/>
57+
<rule ref="SlevomatCodingStandard.Classes.DisallowLateStaticBindingForConstants"/>
58+
<rule ref="SlevomatCodingStandard.Classes.UselessLateStaticBinding"/>
4059
</ruleset>

src/Builder/BuilderInterface.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Squirrel\Queries\Builder;
4+
5+
/**
6+
* Marker for query builder actions so an exception can reference the right origin (higher up in stack trace)
7+
*/
8+
interface BuilderInterface
9+
{
10+
}

src/Builder/CountEntries.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Count query builder as a fluent object - build query and return entries or flattened fields
99
*/
10-
class CountEntries
10+
class CountEntries implements BuilderInterface
1111
{
1212
private DBInterface $db;
1313

src/Builder/DeleteEntries.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* Delete query builder as a fluent object - build query and execute it
1111
*/
12-
class DeleteEntries
12+
class DeleteEntries implements BuilderInterface
1313
{
1414
private DBInterface $db;
1515
private string $table = '';
@@ -81,7 +81,7 @@ private function accidentalDeleteAllCheck(): void
8181
DBInvalidOptionException::class,
8282
[self::class],
8383
'No restricting "where" arguments defined for DELETE' .
84-
'and no override confirmation with "confirmNoWhereRestrictions" call'
84+
'and no override confirmation with "confirmNoWhereRestrictions" call',
8585
);
8686
}
8787
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
namespace Squirrel\Queries\Builder;
4+
5+
use Squirrel\Debug\Debug;
6+
use Squirrel\Queries\Exception\DBInvalidOptionException;
7+
8+
trait FlattenedFieldsWithTypeTrait
9+
{
10+
/**
11+
* @return int[]
12+
*/
13+
public function getFlattenedIntegerFields(): array
14+
{
15+
$values = $this->getFlattenedFields();
16+
17+
foreach ($values as $key => $value) {
18+
if (!\is_integer($value) && !\is_string($value)) {
19+
throw Debug::createException(
20+
DBInvalidOptionException::class,
21+
[BuilderInterface::class],
22+
'Flattened integers requested, but not all values were int or string',
23+
);
24+
}
25+
26+
// Convert non-int values which do not change when type casted
27+
if (
28+
!\is_integer($value)
29+
&& \strval(\intval($value)) === \strval($value)
30+
) {
31+
$values[$key] = \intval($value);
32+
continue;
33+
}
34+
35+
if (!\is_int($value)) {
36+
throw Debug::createException(
37+
DBInvalidOptionException::class,
38+
[BuilderInterface::class],
39+
'Flattened integers requested, but not all values were integers',
40+
);
41+
}
42+
}
43+
44+
return $values;
45+
}
46+
47+
/**
48+
* @return float[]
49+
*/
50+
public function getFlattenedFloatFields(): array
51+
{
52+
$values = $this->getFlattenedFields();
53+
54+
foreach ($values as $key => $value) {
55+
if (\is_int($value)) {
56+
$values[$key] = \floatval($value);
57+
continue;
58+
}
59+
60+
if (!\is_float($value) && !\is_string($value)) {
61+
throw Debug::createException(
62+
DBInvalidOptionException::class,
63+
[BuilderInterface::class],
64+
'Flattened floats requested, but not all values were float or string',
65+
);
66+
}
67+
68+
// Convert non-float values which do not change when type casted
69+
if (
70+
!\is_float($value)
71+
&& \strval(\floatval($value)) === \strval($value)
72+
) {
73+
$values[$key] = \floatval($value);
74+
continue;
75+
}
76+
77+
if (!\is_float($value)) {
78+
throw Debug::createException(
79+
DBInvalidOptionException::class,
80+
[BuilderInterface::class],
81+
'Flattened floats requested, but not all values were floats',
82+
);
83+
}
84+
}
85+
86+
return $values;
87+
}
88+
89+
/**
90+
* @return string[]
91+
*/
92+
public function getFlattenedStringFields(): array
93+
{
94+
$values = $this->getFlattenedFields();
95+
96+
foreach ($values as $key => $value) {
97+
// Integers and floats can be converted to strings without problems
98+
if (
99+
\is_int($value)
100+
|| \is_float($value)
101+
) {
102+
$values[$key] = \strval($value);
103+
continue;
104+
}
105+
106+
if (!\is_string($value)) {
107+
throw Debug::createException(
108+
DBInvalidOptionException::class,
109+
[BuilderInterface::class],
110+
'Flattened strings requested, but not all values were strings',
111+
);
112+
}
113+
}
114+
115+
return $values;
116+
}
117+
118+
/**
119+
* @return bool[]
120+
*/
121+
public function getFlattenedBooleanFields(): array
122+
{
123+
$values = $this->getFlattenedFields();
124+
125+
foreach ($values as $key => $value) {
126+
// Convert non-boolean values which can reasonably be converted to boolean
127+
if (
128+
$value === 0
129+
|| $value === '0'
130+
) {
131+
$values[$key] = false;
132+
continue;
133+
} elseif (
134+
$value === 1
135+
|| $value === '1'
136+
) {
137+
$values[$key] = true;
138+
continue;
139+
}
140+
141+
if (!\is_bool($value)) {
142+
throw Debug::createException(
143+
DBInvalidOptionException::class,
144+
[BuilderInterface::class],
145+
'Flattened booleans requested, but not all values were booleans',
146+
);
147+
}
148+
}
149+
150+
return $values;
151+
}
152+
}

src/Builder/InsertEntry.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Insert query builder as a fluent object - build query and execute it
99
*/
10-
class InsertEntry
10+
class InsertEntry implements BuilderInterface
1111
{
1212
private DBInterface $db;
1313
private string $table = '';

src/Builder/InsertOrUpdateEntry.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* Upsert query builder as a fluent object - build query and execute it
99
*/
10-
class InsertOrUpdateEntry
10+
class InsertOrUpdateEntry implements BuilderInterface
1111
{
1212
private DBInterface $db;
1313
private string $table = '';

src/Builder/SelectEntries.php

+3-33
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
*
1010
* @implements \IteratorAggregate<int,array>
1111
*/
12-
class SelectEntries implements \IteratorAggregate
12+
class SelectEntries implements BuilderInterface, \IteratorAggregate
1313
{
14+
use FlattenedFieldsWithTypeTrait;
15+
1416
private DBInterface $db;
1517

1618
/**
@@ -190,38 +192,6 @@ public function getFlattenedFields(): array
190192
]);
191193
}
192194

193-
/**
194-
* @return int[]
195-
*/
196-
public function getFlattenedIntegerFields(): array
197-
{
198-
return \array_map('intval', $this->getFlattenedFields());
199-
}
200-
201-
/**
202-
* @return float[]
203-
*/
204-
public function getFlattenedFloatFields(): array
205-
{
206-
return \array_map('floatval', $this->getFlattenedFields());
207-
}
208-
209-
/**
210-
* @return string[]
211-
*/
212-
public function getFlattenedStringFields(): array
213-
{
214-
return \array_map('strval', $this->getFlattenedFields());
215-
}
216-
217-
/**
218-
* @return bool[]
219-
*/
220-
public function getFlattenedBooleanFields(): array
221-
{
222-
return \array_map('boolval', $this->getFlattenedFields());
223-
}
224-
225195
public function getIterator(): SelectIterator
226196
{
227197
return new SelectIterator($this->db, [

src/Builder/SelectIterator.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*
1111
* @implements \Iterator<int,array>
1212
*/
13-
class SelectIterator implements \Iterator
13+
class SelectIterator implements \Iterator, BuilderInterface
1414
{
1515
use SelectIteratorTrait;
1616

src/Builder/UpdateEntries.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* Update query builder as a fluent object - build query and execute it
1111
*/
12-
class UpdateEntries
12+
class UpdateEntries implements BuilderInterface
1313
{
1414
private DBInterface $db;
1515
private string $table = '';
@@ -85,7 +85,7 @@ public function writeAndReturnAffectedNumber(): int
8585
DBInvalidOptionException::class,
8686
[self::class],
8787
'No restricting "where" arguments defined for UPDATE' .
88-
'and no override confirmation with "confirmNoWhereRestrictions" call'
88+
'and no override confirmation with "confirmNoWhereRestrictions" call',
8989
);
9090
}
9191

0 commit comments

Comments
 (0)