-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from BaguettePHP/feature/type-interface
Add TypeInterface for escape to non-standard values
- Loading branch information
Showing
7 changed files
with
281 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
parameters: | ||
ignoreErrors: | ||
- | ||
message: "#^Generic type Teto\\\\SQL\\\\PDOAggregate\\<PDO\\|Teto\\\\SQL\\\\PDOInterface\\<PDOStatement\\|Teto\\\\SQL\\\\PDOStatementInterface\\>\\> in PHPDoc tag @param for parameter \\$pdo does not specify all template types of interface Teto\\\\SQL\\\\PDOAggregate\\: S, T$#" | ||
count: 2 | ||
path: src/AbstractStaticQuery.php | ||
|
||
- | ||
message: "#^Type PDO\\|Teto\\\\SQL\\\\PDOInterface\\<PDOStatement\\|Teto\\\\SQL\\\\PDOStatementInterface\\> in generic type Teto\\\\SQL\\\\PDOAggregate\\<PDO\\|Teto\\\\SQL\\\\PDOInterface\\<PDOStatement\\|Teto\\\\SQL\\\\PDOStatementInterface\\>\\> in PHPDoc tag @param for parameter \\$pdo is not subtype of template type S of PDOStatement\\|Teto\\\\SQL\\\\PDOStatementInterface of interface Teto\\\\SQL\\\\PDOAggregate\\.$#" | ||
count: 2 | ||
path: src/AbstractStaticQuery.php | ||
|
||
- | ||
message: "#^Method Teto\\\\SQL\\\\Processor\\\\CallbackProcessor\\:\\:processQuery\\(\\) should return string but returns mixed\\.$#" | ||
count: 1 | ||
path: src/Processor/CallbackProcessor.php | ||
|
||
- | ||
message: "#^Property Teto\\\\SQL\\\\Processor\\\\CallbackProcessor\\:\\:\\$callback with generic interface Teto\\\\SQL\\\\PDOInterface does not specify its types\\: T$#" | ||
count: 1 | ||
path: src/Processor/CallbackProcessor.php | ||
|
||
- | ||
message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(array\\<int\\|string, string\\>\\)\\: string, Closure\\(array\\)\\: int\\|string given\\.$#" | ||
count: 1 | ||
path: src/Processor/PregCallbackReplacer.php | ||
|
||
- | ||
message: "#^Parameter \\#2 \\$matches of method Teto\\\\SQL\\\\ReplacerInterface\\:\\:replaceQuery\\(\\) expects array\\<non\\-empty\\-string, string\\>, array\\<int\\|string, string\\> given\\.$#" | ||
count: 1 | ||
path: src/Processor/PregCallbackReplacer.php | ||
|
||
- | ||
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" | ||
count: 1 | ||
path: src/Type/PgIdentifier.php | ||
|
||
- | ||
message: "#^Method Teto\\\\SQL\\\\Type\\\\PgIdentifier\\:\\:escapeValue\\(\\) should return int\\|string but return statement is missing\\.$#" | ||
count: 1 | ||
path: src/Type/PgIdentifier.php | ||
|
||
- | ||
message: "#^Parameter \\#1 \\$value of method Teto\\\\SQL\\\\Type\\\\PgIdentifier\\:\\:quote\\(\\) expects string, mixed given\\.$#" | ||
count: 1 | ||
path: src/Type/PgIdentifier.php | ||
|
||
- | ||
message: "#^Property Teto\\\\SQL\\\\Type\\\\PgIdentifier\\:\\:\\$types has no type specified\\.$#" | ||
count: 1 | ||
path: src/Type/PgIdentifier.php | ||
|
||
- | ||
message: "#^Part \\$value \\(mixed\\) of encapsed string cannot be cast to string\\.$#" | ||
count: 1 | ||
path: tests/Replacer/Sample/DummyType.php | ||
|
||
- | ||
message: "#^Method Teto\\\\SQL\\\\Type\\\\PgIdentifierTest\\:\\:escapeValuesProvider\\(\\) should return array\\<array\\{string, string\\}\\> but returns array\\{array\\{'', '@column', '\"\"'\\}, array\\{array\\{'foo'\\}, '@column\\[\\]', '\"foo\"'\\}, array\\{array\\{'foo', 'bar'\\}, '@column\\[\\]', '\"foo\",\"bar\"'\\}, array\\{array\\{foo\\: 'bar'\\}, '@column\\[\\]', 'foo AS \"bar\"'\\}, array\\{array\\{'\"foo\"'\\: 'bar'\\}, '@column\\[\\]', '\"foo\" AS \"bar\"'\\}, array\\{array\\{foo\\: null\\}, '@column\\[\\]', 'foo'\\}, array\\{array\\{'\"foo\"'\\: null\\}, '@column\\[\\]', '\"foo\"'\\}, array\\{array\\{foo\\: null, bar\\: 'buz'\\}, '@column\\[\\]', 'foo,bar AS \"buz\"'\\}, \\.\\.\\.\\}\\.$#" | ||
count: 1 | ||
path: tests/Type/PgIdentifierTest.php |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace Teto\SQL\Type; | ||
|
||
use DomainException; | ||
use LogicException; | ||
use Teto\SQL\TypeInterface; | ||
|
||
/** | ||
* Escape identifier (database, table, field and columns names) in PostgreSQL | ||
* | ||
* Please note that this process is highly dependent on the SQL product. | ||
*/ | ||
class PgIdentifier implements TypeInterface | ||
{ | ||
/** @phpstan-var array<non-empty-string,non-empty-string> */ | ||
protected $types = []; | ||
|
||
/** | ||
* @phpstan-param array{'@column'?: non-empty-string, '@column[]'?: non-empty-string, '@table'?: non-empty-string} $type_names | ||
*/ | ||
public function __construct(array $type_names) | ||
{ | ||
$types = []; | ||
|
||
foreach (['@column', '@column[]', '@table'] as $type) { | ||
$key = isset($type_names[$type]) ? $type_names[$type] : $type; | ||
$types[$key] = $type; | ||
} | ||
|
||
$this->types = $types; | ||
} | ||
|
||
public function escapeValue($pdo, $key, $type, $value, &$bind_values) | ||
{ | ||
if (!isset($this->types[$type])) { | ||
throw new LogicException("Passed unexpected type '{$type}', please check your configuration."); | ||
} | ||
|
||
$replaced_type = $this->types[$type]; | ||
if ($replaced_type === '@column') { | ||
if (\is_string($value)) { | ||
return $this->quote($value); | ||
} | ||
|
||
throw new DomainException("Passed unexpected \$value as type '{$type}'. please check your query and parameters."); | ||
} | ||
|
||
if ($replaced_type === '@column[]') { | ||
$columns = []; | ||
if (!\is_array($value)) { | ||
throw new DomainException("Passed unexpected \$value as type '{$type}'. please check your query and parameters."); | ||
} | ||
foreach ($value as $k => $v) { | ||
if (\is_string($k)) { | ||
if ($v === null || $v === '') { | ||
$columns[] = $k; | ||
} else { | ||
$columns[] = "{$k} AS {$this->quote($v)}"; | ||
} | ||
continue; | ||
} elseif (\is_int($k)) { | ||
if (\is_string($v)) { | ||
$columns[] = $this->quote($v); | ||
continue; | ||
} | ||
throw new DomainException("Passed unexpected \$value[{$k}] as type '{$type}'. please check your query and parameters."); | ||
} | ||
|
||
throw new LogicException('Unreachable'); | ||
} | ||
|
||
return \implode(',', $columns); | ||
} | ||
|
||
if ($replaced_type === '@table') { | ||
if (\is_string($value)) { | ||
return $this->quote($value); | ||
} | ||
|
||
throw new DomainException("Passed unexpected \$value as type '{$type}'. please check your query and parameters."); | ||
} | ||
|
||
throw new LogicException("Unreachable, or {$type} is not implemented yet."); | ||
} | ||
|
||
/** | ||
* @phpstan-param string $value | ||
* @phpstan-return non-empty-string | ||
*/ | ||
public function quote($value) | ||
{ | ||
return '"' . $value . '"'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
namespace Teto\SQL; | ||
|
||
interface TypeInterface | ||
{ | ||
/** | ||
* @param \PDO|\Teto\SQL\PDOInterface $pdo | ||
* @template S of \PDOStatement|\Teto\SQL\PDOStatementInterface | ||
* @phpstan-param \PDO|\Teto\SQL\PDOInterface<S> $pdo | ||
* @param string $key | ||
* @param string $type | ||
* @param mixed $value | ||
* @param array<mixed> $bind_values | ||
* @return string|int | ||
*/ | ||
public function escapeValue($pdo, $key, $type, $value, &$bind_values); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
namespace Teto\SQL\Replacer\Sample; | ||
|
||
use Teto\SQL\TypeInterface; | ||
|
||
class DummyType implements TypeInterface | ||
{ | ||
public function escapeValue($pdo, $key, $type, $value, &$bind_values) | ||
{ | ||
assert(\is_string($value)); | ||
return "[{$value}] is a dummy value."; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
namespace Teto\SQL\Type; | ||
|
||
use Teto\SQL\DummyPDO; | ||
use Yoast\PHPUnitPolyfills\TestCases\TestCase; | ||
|
||
class PgIdentifierTest extends TestCase | ||
{ | ||
/** @var PgIdentifier */ | ||
private $subject; | ||
|
||
public function set_up() | ||
{ | ||
parent::set_up(); | ||
|
||
$this->subject = new PgIdentifier([]); | ||
} | ||
|
||
/** | ||
* @dataProvider escapeValuesProvider | ||
* @phpstan-param string|array<string> $input | ||
* @param string $type | ||
* @param string $expected | ||
* @return void | ||
*/ | ||
public function testEscapeValue($input, $type, $expected) | ||
{ | ||
$pdo = new DummyPDO(); | ||
$bind_values = []; | ||
$this->assertSame($expected, $this->subject->escapeValue($pdo, ':key', $type, $input, $bind_values)); | ||
$this->assertEquals([], $bind_values); | ||
} | ||
|
||
/** | ||
* @return array<array{string|array<?string>,string,string}> | ||
*/ | ||
public function escapeValuesProvider() | ||
{ | ||
return [ | ||
['' , '@column', '""'], | ||
[['foo'] , '@column[]', '"foo"'], | ||
[['foo','bar'] , '@column[]', '"foo","bar"'], | ||
[['foo' => 'bar'] , '@column[]', 'foo AS "bar"'], | ||
[['"foo"' => 'bar'] , '@column[]', '"foo" AS "bar"'], | ||
[['foo' => null] , '@column[]', 'foo'], | ||
[['"foo"' => null] , '@column[]', '"foo"'], | ||
[['foo' => null, 'bar' => 'buz'] , '@column[]', 'foo,bar AS "buz"'], | ||
[['"foo"' => null, 'bar' => ''] , '@column[]', '"foo",bar'], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider quoteValueProvider | ||
* @param string $input | ||
* @param string $expected | ||
* @return void | ||
*/ | ||
public function testQuote($input, $expected) | ||
{ | ||
$this->assertSame($expected, $this->subject->quote($input)); | ||
} | ||
|
||
/** | ||
* @return array<array{string,string}> | ||
*/ | ||
public function quoteValueProvider() | ||
{ | ||
return [ | ||
['' , '""'], | ||
['foo' , '"foo"'], | ||
]; | ||
} | ||
} |