Skip to content

Commit 7e78af4

Browse files
authored
Allow custom functions and rewrite rules (#106)
* Allow adding custom functions/rewrite rules * Update README.md * StyleCI changes
1 parent a6e9179 commit 7e78af4

File tree

6 files changed

+105
-30
lines changed

6 files changed

+105
-30
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ In order to reduce clutter it is preferable to create a separate Service Provide
3434
}
3535
}
3636
```
37+
3738
1. Add a line to `app/Providers/AppServiceProvider.php` within the `register()` method:
3839
```php
3940
$this->app->register(MySQLiteServiceProvider::class);
@@ -93,6 +94,32 @@ In order to reduce clutter it is preferable to create a separate Service Provide
9394
#### Comparison
9495
- [least(mixed ...)](https://github.com/Vectorface/MySQLite/blob/master/src/Vectorface/MySQLite/MySQL/Comparison.php)
9596

97+
# Custom Functionality
98+
While this package aims to cover common functionality, there are times when you need support for a function quickly or a custom function that is unique to your application. This is easy to do with two methods from the `boot()` method of your service provider class:
99+
100+
```php
101+
<?php
102+
103+
class MySQLiteServiceProvider extends ServiceProvider
104+
{
105+
...
106+
107+
public function boot()
108+
{
109+
$connection = $this->app->get('db')->connection();
110+
111+
if ($connection->getDriverName() === 'sqlite') {
112+
$connection
113+
->addRewriteRule('/CURDATE\(\)/', "date('now')")
114+
->addFunction('CURDATE', fn() => CarbonImmutable::today()->toDateString(), 0);
115+
}
116+
}
117+
}
118+
```
119+
120+
- `addRewriteRule()` will replace a string in your query using regex, should Sqlite have a native function that could be used as a 1:1 replacement.
121+
- `addFunction()` uses PDO `sqliteCreateFunction()` to register a custom function with PHP in the event that Sqlite doesn't have a drop-in replacement or if logic is more complicated. [Read More][sqlitecreatefunction].
122+
96123
# Contributing
97124
Want to file a bug, contribute some code, improve documentation, or request a feature? Awesome Sauce! Read up on our guidelines for [contributing][contributing]. All contributions must follow our [Code of Conduct][codeofconduct].
98125

@@ -114,3 +141,4 @@ License: (MIT) https://github.com/Vectorface/MySQLite/blob/master/LICENSE
114141
[contributing]: ./.github/contributing.md
115142
[issue]: https://github.com/spam-n-eggs/laravel-mysqlite/issues
116143
[codeofconduct]:./.github/CODE_OF_CONDUCT.md
144+
[sqlitecreatefunction]: https://www.php.net/manual/en/pdo.sqlitecreatefunction.php

src/Mhorninger/MySQLite/MethodRewriteConstants.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
class MethodRewriteConstants
66
{
77
const METHOD_REPLACEMENTS = [
8-
'/(DATE_ADD)(?=.*?, INTERVAL.*?\\))/' => 'datetime',
9-
'/INTERVAL (?=.*?\\))/' => '\'+',
10-
'/INTERVAL (?=.*?\\))/' => '\'+',
11-
'/SECOND(?=\\))/' => 'seconds\'',
12-
'/MINUTE(?=\\))/' => 'minutes\'',
13-
'/HOUR(?=\\))/' => 'hours\'',
14-
'/DAY(?=\\))/' => 'days\'',
15-
'/WEEK(?=\\))/' => 'weeks\'',
16-
'/MONTH(?=\\))/' => 'months\'',
17-
'/YEAR(?=\\))/' => 'years\'',
18-
'/LEFT(?=.*?, .*?\\))/' => '`LEFT`',
19-
'/RIGHT(?=.*?, .*?\\))/' => '`RIGHT`',
8+
['/(DATE_ADD)(?=.*?, INTERVAL.*?\\))/', 'datetime'],
9+
['/INTERVAL (?=.*?\\))/', '\'+'],
10+
['/INTERVAL (?=.*?\\))/', '\'+'],
11+
['/SECOND(?=\\))/', 'seconds\''],
12+
['/MINUTE(?=\\))/', 'minutes\''],
13+
['/HOUR(?=\\))/', 'hours\''],
14+
['/DAY(?=\\))/', 'days\''],
15+
['/WEEK(?=\\))/', 'weeks\''],
16+
['/MONTH(?=\\))/', 'months\''],
17+
['/YEAR(?=\\))/', 'years\''],
18+
['/LEFT(?=.*?, .*?\\))/', '`LEFT`'],
19+
['/RIGHT(?=.*?, .*?\\))/', '`RIGHT`'],
2020
];
2121
}

src/Mhorninger/MySQLite/MySQLite.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
/**
1414
* MySQLite is the extension Vectorface's MySQLite extension.
15+
*
1516
* @see \Vectorface\MySQLite\MySQLite
1617
*/
1718
class MySQLite extends \Vectorface\MySQLite\MySQLite
@@ -46,14 +47,14 @@ protected static function getPublicMethodData()
4647
/**
4748
* Add MySQLite compatibility functions to a PDO object.
4849
*
49-
* @param \PDO $pdo A PDO instance to which the MySQLite compatibility functions should be added.
50-
* @param string[] $fnList A list of functions to create on the SQLite database. (Omit to create all.)
50+
* @param \PDO $pdo A PDO instance to which the MySQLite compatibility functions should be added.
51+
* @param string[] $fnList A list of functions to create on the SQLite database. (Omit to create all.)
5152
* @return \PDO Returns a reference to the PDO instance passed in to the function.
5253
*/
5354
public static function &createFunctions(\PDO &$pdo, array $fnList = null)
5455
{
5556
if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'sqlite') {
56-
throw new InvalidArgumentException('Expecting a PDO instance using the SQLite driver');
57+
throw new \InvalidArgumentException('Expecting a PDO instance using the SQLite driver');
5758
}
5859

5960
foreach (static::getPublicMethodData() as $method => $paramCount) {
@@ -66,10 +67,10 @@ public static function &createFunctions(\PDO &$pdo, array $fnList = null)
6667
/**
6768
* Register a method as an SQLite funtion.
6869
*
69-
* @param PDO $pdo A PDO instance to which the MySQLite compatibility functions should be added.
70-
* @param string $method The internal method name.
71-
* @param int $paramCount The suggested parameter count.
72-
* @param string[] $fnList A list of functions to create on the SQLite database, or empty for all.
70+
* @param PDO $pdo A PDO instance to which the MySQLite compatibility functions should be added.
71+
* @param string $method The internal method name.
72+
* @param int $paramCount The suggested parameter count.
73+
* @param string[] $fnList A list of functions to create on the SQLite database, or empty for all.
7374
* @return bool Returns true if the method was registed. False otherwise.
7475
*/
7576
protected static function registerMethod(\PDO &$pdo, $method, $paramCount, array $fnList = null)

src/Mhorninger/SQLite/MySQLiteConnection.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Mhorninger\SQLite;
44

5+
use Illuminate\Support\Collection;
56
use Mhorninger\MySQLite\MethodRewriteConstants;
67
use Mhorninger\MySQLite\MySQLite;
78
use Mhorninger\MySQLite\SubstitutionConstants;
@@ -11,27 +12,31 @@
1112
class MySQLiteConnection extends \Illuminate\Database\SQLiteConnection
1213
{
1314
const ESCAPE_CHARS = ['`', '[', '"'];
15+
private Collection $rewriteRules;
1416

1517
/**
1618
* Create a new database connection instance.
1719
*
18-
* @param \PDO|\Closure $pdo
19-
* @param string $database
20-
* @param string $tablePrefix
21-
* @param array $config
20+
* @param \PDO|\Closure $pdo
21+
* @param string $database
22+
* @param string $tablePrefix
23+
* @param array $config
2224
* @return void
2325
*/
2426
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
2527
{
2628
parent::__construct($pdo, $database, $tablePrefix, $config);
29+
30+
$this->rewriteRules = collect(MethodRewriteConstants::METHOD_REPLACEMENTS);
31+
2732
//Make sure the PDO is actually a PDO and not a closure.
2833
$this->pdo = $this->getPdo();
2934
$this->pdo = MySQLite::createFunctions($this->pdo);
3035
}
3136

3237
public function run($query, $bindings, \Closure $callback)
3338
{
34-
//Skip on inserts.
39+
// Skip on inserts.
3540
$insertRegex = '/INSERT INTO.*?;/';
3641
if (0 == preg_match($insertRegex, $query)) {
3742
$query = $this->methodRewrite($query);
@@ -46,26 +51,41 @@ private function scanQueryForConstants($query)
4651
$reflection = new ReflectionClass(SubstitutionConstants::class);
4752
$constants = $reflection->getConstants();
4853
$placeholders = array_keys($constants);
54+
4955
foreach ($placeholders as $placeholder) {
5056
$searchFor = '/'.preg_quote($placeholder).'(?!\\(|\\w|\\))/';
5157
$query = preg_replace($searchFor, "'".$constants[$placeholder]."'", $query);
5258
}
59+
5360
$reflection = new ReflectionClass(UnquotedSubstitutionConstants::class);
5461
$methodConstants = $reflection->getConstants();
5562
$placeholders = array_keys($methodConstants);
63+
5664
foreach ($placeholders as $placeholder) {
5765
$query = str_replace($placeholder, $methodConstants[$placeholder], $query);
5866
}
5967

6068
return $query;
6169
}
6270

63-
private function methodRewrite($query)
71+
public function addFunction(string $function, callable $callback, int $paramCount): self
6472
{
65-
foreach (array_keys(MethodRewriteConstants::METHOD_REPLACEMENTS) as $regex) {
66-
$query = preg_replace($regex, MethodRewriteConstants::METHOD_REPLACEMENTS[$regex], $query);
67-
}
73+
$this->pdo->sqliteCreateFunction($function, $callback, $paramCount);
6874

69-
return $query;
75+
return $this;
76+
}
77+
78+
public function addRewriteRule($regex, $replacement): self
79+
{
80+
$this->rewriteRules->push([$regex, $replacement]);
81+
82+
return $this;
83+
}
84+
85+
private function methodRewrite($query)
86+
{
87+
return $this
88+
->rewriteRules
89+
->reduce(fn ($query, $rule) => preg_replace($rule[0], $rule[1], $query), $query);
7090
}
7191
}

test/Mhorninger/MySQLite/MiscellaneousMethodTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Mhorninger\MySQLite;
44

5+
use Carbon\CarbonImmutable;
56
use Mhorninger\TestCase;
67

78
class MiscellaneousMethodTest extends TestCase
@@ -20,4 +21,24 @@ public function testInetNtoaNull()
2021
$result = $this->conn->selectOne($query);
2122
$this->assertNull($result->value);
2223
}
24+
25+
public function testAddFunction()
26+
{
27+
$date = CarbonImmutable::today();
28+
29+
$this->conn->addFunction('TEST_DATE', fn () => $date, 0);
30+
31+
$result = $this->selectValue('SELECT TEST_DATE()');
32+
$this->assertEquals($date, $result);
33+
}
34+
35+
public function testAddRewriteRule()
36+
{
37+
$date = CarbonImmutable::today();
38+
39+
$this->conn->addRewriteRule('/TEST_DATE\(\)/', "date('now')");
40+
41+
$result = $this->selectValue('SELECT TEST_DATE()');
42+
$this->assertEquals($date->toDateString(), $result);
43+
}
2344
}

test/Mhorninger/TestCase.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class TestCase extends BaseTestCase
1010
{
11-
protected $conn = null;
11+
protected ?MySQLiteConnection $conn = null;
1212

1313
public function setUp(): void
1414
{
@@ -18,4 +18,9 @@ public function setUp(): void
1818
//Set up the connection.
1919
$this->conn = new MySQLiteConnection($pdo);
2020
}
21+
22+
public function selectValue(string $query)
23+
{
24+
return collect($this->conn->select($query)[0])->values()->first();
25+
}
2126
}

0 commit comments

Comments
 (0)