Commit ab7eb626 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Evaluate.php: created

parent 1260c4e0
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 1/12/16
* Time: 4:36 PM
*/
namespace qfq;
use qfq;
use qfq\store\Store;
require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/Database.php');
class Evaluate {
private $store = null;
private $db = null;
private $startDelimiter = '';
private $startDelimiterLength = 0;
private $endDelimiter = '';
private $endDelimiterLength = 0;
private $sqlKeywords = array('SELECT ', 'INSERT ', 'DELETE ', 'UPDATE ', 'SHOW ');
public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
$this->store = $store;
$this->db = $db;
$this->startDelimiter = $startDelimiter;
$this->startDelimiterLength = strlen($startDelimiter);
$this->endDelimiter = $endDelimiter;
$this->endDelimiterLength = strlen($endDelimiter);
}
/**
* @param $line
* @throws UserException
*/
public function parse($line, $recursion = 0) {
if ($recursion > 4) {
throw new qfq\exceptions\UserException("Recursion too deep: $line", ERROR_RECURSION_TOO_DEEP);
}
$posFirstClose = strpos($line, $this->endDelimiter);
while ($posFirstClose !== false) {
$posMatchOpen = strrpos(substr($line, 0, $posFirstClose), $this->startDelimiter);
if ($posMatchOpen === false) {
throw new \qfq\exceptions\UserException("Missing open delimiter: $line", ERROR_MISSING_OPEN_DELIMITER);
}
$pre = substr($line, 0, $posMatchOpen);
$post = substr($line, $posFirstClose + $this->endDelimiterLength);
$match = substr($line, $posMatchOpen + $this->startDelimiterLength, $posFirstClose - $posMatchOpen - $this->startDelimiterLength);
$evaluated = $this->substitute($match);
// If an array is returned, break everything and return this assoc array.
if (is_array($evaluated)) {
return $evaluated;
}
// More to substitute in the new evaluated result? Start recursion just with the new result..
if (strpos($evaluated, $this->endDelimiter) !== false) {
$evaluated = $this->parse($evaluated, $recursion + 1);
}
$line = $pre . $evaluated . $post;
$posFirstClose = strpos($line, $this->endDelimiter);
}
return $line;
}
/**
* Tries to substitute $token.
* Token might be
* a) fetched from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:ALNUMX'
* b) Result of a fired SQL statement.
* If neither a) or b) match, return the token itself, surrounded by single ticks, to emphase that substition failed.
*
* @param $token
* @return array|mixed|null|string
* @throws exceptions\DbException
*/
public function substitute($token) {
$sqlMode = ROW_IMPLODE_ALL;
$token = trim($token);
// just to extract the first token: check if this is a SQL Statement
$arr = explode(' ', $token, 2);
if ($token[0] === '!') {
$token = substr($token, 1);
$arr[0] = substr($arr[0], 1);
$sqlMode = ROW_REGULAR;
}
// SQL Statement?
if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
return $this->db->sql($token, $sqlMode);
}
// explode for: <key>:<store priority>:<sanatize class>
$arr = explode(':', $token, 3);
if (!isset($arr[1]))
$arr[1] = null;
if (!isset($arr[2]))
$arr[2] = null;
// search for value in stores
$value = $this->store->getVar($arr[0], $arr[1], $arr[2]);
// nothing replaced: put ticks around, to sanatize strings for SQL statements. Nothing to substitute is not a wished situation.
return ($value === false) ? "'" . $token . "'" : $value;
}
}
// Hallo {{SELECT id FROM Form ='{{form}}'}} wird {{SELECT {{SHOW Table}} FROM id={{r}} }} NEW {{SQL}} ENDE
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 1/15/16
* Time: 8:24 AM
*/
//namespace qfq;
//use qfq\store\Store;
//use qfq
require_once(__DIR__ . '/AbstractDatabaseTest.php');
require_once(__DIR__ . '/../../qfq/Evaluate.php');
require_once(__DIR__ . '/../../qfq/Database.php');
require_once(__DIR__ . '/../../qfq/store/Store.php');
class EvaluateTest extends AbstractDatabaseTest {
public function testVars() {
$eval = new \qfq\Evaluate($this->store, $this->db);
// no variable
$this->assertEquals('nothing', $eval->parse('nothing'));
$this->assertEquals('TestFormName', $eval->parse('{{form:T}}'));
// pure digit variable
$this->store->setVar('a', '1', 'C');
$this->assertEquals('1', $eval->parse('{{a:C}}'));
// variable contains variable
$this->store->setVar('a', '{{b:C}}', 'C');
$this->store->setVar('b', '1234', 'C');
$this->assertEquals('1234', $eval->parse('{{a:C:all}}'));
// multiple vaules, Recursive
$this->store->setVar('m1', 'M1=[{{m2:C:all}}]', 'C');
$this->store->setVar('m2', 'M2=[{{m3:C:all}}]', 'C');
$this->store->setVar('m3', 'Some really nice text', 'C');
$this->assertEquals('These are the variables > m1:M1=[M2=[Some really nice text]], m2:M2=[Some really nice text], m3:Some really nice text - end outer line',
$eval->parse('These are the variables > m1:{{m1:C:all}}, m2:{{m2:C:all}}, m3:{{m3:C:all}} - end outer line'));
}
public function testDb() {
$eval = new \qfq\Evaluate($this->store, $this->db);
// database: lower case
$this->assertEquals('1DoeJohnmalec2SmithJanefemalea,c', $eval->parse('{{select * from Person where id < 3 order by id}}'));
// database: upper case
$this->assertEquals('1DoeJohnmalec2SmithJanefemalea,c', $eval->parse('{{SELECT * FROM Person WHERE id < 3 ORDER BY id}}'));
$this->store->setVar('sql', 'SELECT * FROM Person WHERE id < 3 ORDER BY id', 'C');
$this->assertEquals('SELECT * FROM Person WHERE id < 3 ORDER BY id', $eval->parse('{{sql:C:all}}'));
$this->assertEquals('1DoeJohnmalec2SmithJanefemalea,c', $eval->parse('{{{{sql:C:all}}}}'));
$expected = [
[
'id' => '1',
'name' => 'Doe'
],
[
'id' => '2',
'name' => 'Smith'
],
];
$this->assertSame($expected, $eval->parse('{{!SELECT id, name FROM Person WHERE id < 3 ORDER BY id}}'));
// INSERT: Use eval() to write a record
$eval->parse('{{INSERT INTO Person (name, firstname) VALUES (\'Sinatra\', \'Frank\')}}');
$this->assertEquals('Frank Sinatra', $eval->parse('{{SELECT firstname, " ", name FROM Person WHERE name="Sinatra" AND firstname="Frank"}}'));
// INSERT & DELETE: Use eval() to write a record
$eval->parse('{{INSERT INTO Person (name, firstname) VALUES (\'Sinatra\', \'Frank\')}}');
$eval->parse('{{DELETE FROM Person WHERE name="Sinatra" AND firstname="Frank" LIMIT 1}}');
$this->assertEquals('1', $eval->parse('{{SELECT count(id) FROM Person WHERE name="Sinatra" AND firstname="Frank"}}'));
// UPDATE
$eval->parse('{{UPDATE Person set name=\'Zappa\' WHERE name="Sinatra" AND firstname="Frank" LIMIT 1}}');
$this->assertEquals('1', $eval->parse('{{SELECT count(id) FROM Person WHERE name="Zappa" AND firstname="Frank"}}'));
// SHOW tables
$expected = "idbigint(20)NOPRIauto_incrementnamevarchar(128)YESfirstnamevarchar(128)YESgenderenum('','male','female')NOgroupsset('','a','b','c')NO";
$this->assertEquals($expected, $eval->parse('{{SHOW COLUMNS FROM Person}}'));
}
public function testDbVars() {
$eval = new \qfq\Evaluate($this->store, $this->db);
$eval->parse('{{INSERT INTO Person (name, firstname) VALUES (\'Holiday\', \'Billie\')}}');
$this->store->setVar('a', '{{b:C:all}}', 'C');
$this->store->setVar('b', 'Holiday', 'C');
$this->assertEquals('Holiday', $eval->parse('{{SELECT name FROM Person WHERE name=\'{{a:C:all}}\'}}'));
}
protected function setUp() {
$this->store = \qfq\store\Store::getInstance('form=TestFormName');
parent::setUp();
$this->executeSQLFile(__DIR__ . '/fixtures/Generic.sql', true);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment