Skip to content

Commit ddcff9d

Browse files
committed
Work in progress on Twig
1 parent 0a7ee81 commit ddcff9d

File tree

6 files changed

+163
-1
lines changed

6 files changed

+163
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"mouf/utils.value.value-interface": "~1.0",
1919
"mouf/utils.common.paginable-interface": "~1.0",
2020
"mouf/utils.common.sortable-interface": "~1.0",
21-
"mouf/schema-analyzer": "~1.0"
21+
"mouf/schema-analyzer": "~1.0",
22+
"twig/twig": "~1.14"
2223
},
2324
"require-dev": {
2425
"phpunit/phpunit": "~4.0",

src/Mouf/Database/MagicQuery.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Mouf\Database;
44

55
use Doctrine\Common\Cache\VoidCache;
6+
use Mouf\Database\MagicQuery\Twig\SqlTwigEnvironmentFactory;
67
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
78
use SQLParser\Node\ColRef;
89
use SQLParser\Node\Equal;
@@ -26,6 +27,7 @@ class MagicQuery
2627
private $connection;
2728
private $cache;
2829
private $schemaAnalyzer;
30+
private $twigEnvironment;
2931

3032
/**
3133
* @param \Doctrine\DBAL\Connection $connection
@@ -43,6 +45,9 @@ public function __construct($connection = null, $cache = null, SchemaAnalyzer $s
4345
if ($schemaAnalyzer) {
4446
$this->schemaAnalyzer = $schemaAnalyzer;
4547
}
48+
if ($this->connection) {
49+
$this->twigEnvironment = SqlTwigEnvironmentFactory::getTwigEnvironment($this->connection);
50+
}
4651
}
4752

4853
/**
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
namespace Mouf\Database\MagicQuery\Twig;
3+
4+
use Doctrine\DBAL\Connection;
5+
6+
/**
7+
* Class in charge of creating the Twig environment
8+
*/
9+
class SqlTwigEnvironmentFactory
10+
{
11+
public static function getTwigEnvironment(Connection $connection = null) {
12+
$stringLoader = new StringLoader();
13+
14+
$options = array(
15+
// The cache directory is in the temporary directory and reproduces the path to the directory (to avoid cache conflict between apps).
16+
'cache' => self::getCacheDirectory(),
17+
'strict_variables' => true
18+
);
19+
20+
$twig = new \Twig_Environment($stringLoader, $options);
21+
22+
if ($connection !== null) {
23+
24+
$twig->getExtension('core')->setEscaper('sql', function(\Twig_Environment $env, $string, $charset) use ($connection) {
25+
var_dump($string);
26+
// ARGH! Escapers seems to be not called if $string is empty!
27+
if ($string === null) {
28+
return "null";
29+
} else {
30+
return $connection->quote($string);
31+
}
32+
});
33+
34+
// SQL identifier (table or column names....)
35+
$twig->getExtension('core')->setEscaper('sqli', function(\Twig_Environment $env, $string, $charset) use ($connection) {
36+
return $connection->quoteIdentifier($string);
37+
});
38+
39+
} else {
40+
$twig->getExtension('core')->setEscaper('sql', function(\Twig_Environment $env, $string, $charset) use ($connection) {
41+
if ($string === null) {
42+
return "null";
43+
} else {
44+
return "'".addslashes($string)."'";
45+
}
46+
});
47+
48+
$twig->getExtension('core')->setEscaper('sqli', function(\Twig_Environment $env, $string, $charset) use ($connection) {
49+
// Note: we don't know how to escape backticks in a column name. In order to avoid injection,
50+
// we remove any backticks.
51+
return "`".str_replace('`', '', $string)."`";
52+
});
53+
}
54+
55+
// Default autoescape mode: sql
56+
$twig->addExtension(new \Twig_Extension_Escaper('sql'));
57+
58+
return $twig;
59+
}
60+
61+
private static function getCacheDirectory() {
62+
// If we are running on a Unix environment, let's prepend the cache with the user id of the PHP process.
63+
// This way, we can avoid rights conflicts.
64+
if (function_exists('posix_geteuid')) {
65+
$posixGetuid = '_'.posix_geteuid();
66+
} else {
67+
$posixGetuid = '';
68+
}
69+
$cacheDirectory = rtrim(sys_get_temp_dir(), '/\\').'/magicquerysqltwigtemplate'.$posixGetuid.str_replace(":", "", __DIR__);
70+
71+
return $cacheDirectory;
72+
}
73+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
namespace Mouf\Database\MagicQuery\Twig;
3+
4+
5+
/**
6+
* This loader completely bypasses the loader mechanism, by directly passing the key as a template.
7+
* Useful in our very case.
8+
*
9+
* This is a reimplementation of Twig's String loader that has been deprecated.
10+
* We enable it back in our case because there won't be a million of different cache keys.
11+
* And yes, we know what we are doing :)
12+
*/
13+
class StringLoader implements \Twig_LoaderInterface
14+
{
15+
/**
16+
* {@inheritdoc}
17+
*/
18+
public function getSource($name)
19+
{
20+
return $name;
21+
}
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function getCacheKey($name)
26+
{
27+
return $name;
28+
}
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function isFresh($name, $time)
33+
{
34+
return true;
35+
}
36+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Mouf\Database;
4+
5+
use Doctrine\Common\Cache\ArrayCache;
6+
use Doctrine\DBAL\Schema\Schema;
7+
use Mouf\Database\MagicQuery\Twig\SqlTwigEnvironmentFactory;
8+
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
9+
10+
class SqlTwigEnvironmentFactoryTest extends \PHPUnit_Framework_TestCase
11+
{
12+
public function testWithConnection()
13+
{
14+
$config = new \Doctrine\DBAL\Configuration();
15+
// TODO: put this in conf variable
16+
$connectionParams = array(
17+
'url' => 'mysql://root:@localhost/',
18+
);
19+
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
20+
21+
$twigEnvironment = SqlTwigEnvironmentFactory::getTwigEnvironment($conn);
22+
$this->doTestTwig($twigEnvironment);
23+
}
24+
25+
/**
26+
* We need to run this test in a separate process because Twig seems to be enable to register several escapers
27+
* with the same name.
28+
*
29+
* @runInSeparateProcess
30+
*/
31+
/*public function testWithoutConnection()
32+
{
33+
$twigEnvironment = SqlTwigEnvironmentFactory::getTwigEnvironment();
34+
$this->doTestTwig($twigEnvironment);
35+
}*/
36+
37+
38+
private function doTestTwig(\Twig_Environment $twig) {
39+
$result = $twig->render("SELECT * FROM toto WHERE id = {{ id }}", ["id"=>null]);
40+
$this->assertEquals("SELECT * FROM toto WHERE id = null", $result);
41+
42+
$result = $twig->render("SELECT * FROM toto WHERE id = {{ id }}", [ "id" => "myid" ]);
43+
$this->assertEquals("SELECT * FROM toto WHERE id = 'myid'", $result);
44+
45+
}
46+
}

tests/Mouf/Database/MagicQueryTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public function testStandardSelect()
4141

4242
public function testWithCache() {
4343
$config = new \Doctrine\DBAL\Configuration();
44+
// TODO: put this in conf variable
4445
$connectionParams = array(
4546
'url' => 'mysql://root:@localhost/',
4647
);

0 commit comments

Comments
 (0)