Is there any way to use mysql function in extbase querying? - php

What can i do if i want to something like following using extbase model query (Using Object Relational Model).
SELECT uid FROM table WHERE fIND_IN_SET('4',column_name);
OR something like
SELECT SUM(column_name) FROM table WHERE 1
Note : I don't want to use custom query using statement() method

If you specified why you don't "want" to use a custom statement you would realize that you are mistaken. Your situation is the exact reason why statement() exists in the first place. Trying to avoid using it at almost any cost is unreasonable as the SQL abilities of extbase are far from covering all use cases. So in summary: use extbase API where logical and an obvious good choice but don't shy away from statement() where it's use is the obvious best choice just because usign extbase API "looks nicer" or it seems like with the API you're "using the framework to it's full potential".

As of now, there is neither an equivalent for the FIND_IN_SET function, nor for aggregate functions.
You will only get around this only with a custom statement or by writing your own extension of the TYPO3\CMS\Extbase\Persistence\Generic\Query class. There are, of course, security considerations which you'll have to mind with a custom statement(). However, the same security implications apply with the class extension.
To be more specific on the FIND_IN_SET function: You can't just use the like() method. If you're searching for id 1, for example, you would find it in a set that consists of any of 10,11,12,13 and so on. Furthermore can't get around that problem either because the like() method only accepts property names and doesn't let you wrap column names in functions.

According to your question: I don't think there is any way to use mysql functions in extbase without using statement().
According to your example: You can try $query->like('columnName', "%{4}%").

This is possible but needs a bit effort. I have done that in my news extension. The code looks like that
$query = // your $query->execute();
$queryParser = $this->objectManager->get(Typo3DbQueryParser::class);
list($hash, $parameters) = $queryParser->preparseQuery($query);
$statementParts = $queryParser->parseQuery($query);
// Limit and offset are not cached to allow caching of pagebrowser queries.
$statementParts['limit'] = ((int)$query->getLimit() ?: null);
$statementParts['offset'] = ((int)$query->getOffset() ?: null);
$tableNameForEscape = (reset($statementParts['tables']) ?: 'foo');
foreach ($parameters as $parameterPlaceholder => $parameter) {
if ($parameter instanceof LazyLoadingProxy) {
$parameter = $parameter->_loadRealInstance();
}
if ($parameter instanceof \DateTime) {
$parameter = $parameter->format('U');
} elseif ($parameter instanceof DomainObjectInterface) {
$parameter = (int)$parameter->getUid();
} elseif (is_array($parameter)) {
$subParameters = [];
foreach ($parameter as $subParameter) {
$subParameters[] = $GLOBALS['TYPO3_DB']->fullQuoteStr($subParameter, $tableNameForEscape);
}
$parameter = implode(',', $subParameters);
} elseif ($parameter === null) {
$parameter = 'NULL';
} elseif (is_bool($parameter)) {
return ($parameter === true ? 1 : 0);
} else {
$parameter = $GLOBALS['TYPO3_DB']->fullQuoteStr((string)$parameter, $tableNameForEscape);
}
$statementParts['where'] = str_replace($parameterPlaceholder, $parameter, $statementParts['where']);
}
$statementParts = [
'selectFields' => implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']),
'fromTable' => implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']),
'whereClause' => (!empty($statementParts['where']) ? implode('', $statementParts['where']) : '1')
. (!empty($statementParts['additionalWhereClause'])
? ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'])
: ''
),
'orderBy' => (!empty($statementParts['orderings']) ? implode(', ', $statementParts['orderings']) : ''),
'limit' => ($statementParts['offset'] ? $statementParts['offset'] . ', ' : '')
. ($statementParts['limit'] ? $statementParts['limit'] : '')
];
$sql = $GLOBALS['TYPO3_DB']->SELECTquery(
$statementParts['selectFields'],
$statementParts['fromTable'],
$statementParts['whereClause'],
'',
$statementParts['orderBy'],
$statementParts['limit']
);
return $sql;
By using the DataMapper you can map the raw result back to models if you need that as well. The code for that looks like that
$dataMapper = $objectManager->get(DataMapper::class);
$records = $dataMapper->map($className, $rows);

Related

best way to pass a variable number of arguments to a php function

What is the best way to pass a variable number of arguments to a php function? I mean, suppose i have the following:
function my_func($a, $b, $c) {
$q = 'SELECT ' . $a . ' FROM ' . $b . ' WHERE status IS NULL';
}
my_func('id', 'table');
my_func('id', 'table', ' AND x = 1');
I've read about func_get_arg(), but if i call func_get_arg(2) in the first situation, i will get a, Argument 2 not passed to function error.
IMPORTANT: this query is not performed with user-passed arguments, so no injection hazzards! It is performed with controlled arguments given by me and its function is to check if that value is valid within a combination of foreign keys! So please no sarcastic 'injection paradise' comments, thank you.
Well i do not know if it's best, but i like to pass the array as argument and then work with it in my function. Here is one example:
function my_query($query = array())
{
// select and from are required to exist
if(!empty($query) && array_key_exists('select', $query) && array_key_exists('from', $query))
{
$q = "select {$query['select']}";
$q .= " from {$query['from']}";
foreach($query as $key => $val)
{
// Don't want to include select and from once again (also do not unset before in case need to run all of this once again)
if($key != 'select' && $key != 'from')
{
// Search if key has underscore and replace it with space for valid query
if(strpos($key, '_') !== false)
$key = str_replace('_', ' ', $key);
// Build query with spaces and all
$q .= " " . $key . " " . $val;
}
}
// Run query here using $q
}
}
And you can pass in array as you like:
$query = array(
'select' => '*',
'from' => 'users',
'where' => 'age > 25',
'order by' => 'id'
);
// Or
$query = array();
$query['select'] = '*';
$query['from'] = 'users';
$query['where'] = 'age > 25';
$query['order_by'] = 'id';
my_query($query);
// Would return us something like this
string(46) "select * from users where age > 25 order by id"
But using this you have to maintain right order in your array or write ordering and validation code in your function.
Since you have mentioned that your function does not deal with user-passed arguments.. I am suggesting this..
FYI : I just used an echo inside that for demonstration purposes.. you can change that later.
<?php
function my_func() {
echo $q = 'SELECT ' . func_get_arg(0) . ' FROM ' . func_get_arg(1) . ' WHERE status IS NULL';
}
my_func('id', 'table');
The above displays...
SELECT id FROM table WHERE status IS NULL
The arguments start from 0 index, so you should probably do.. func_get_arg(1) to get the second argument.

php - foreach loop - correct (most efficient way to handle a first case scenario )

I have a function that builds a MySQL query from the supplied arguments. My current code is:
($args can be an empty array or up to a set of 5 field_names=>array_of_ids ...)
if( !(empty( $args )) )
{
$flag = 0;
$sql_append = '';
foreach( $args as $field_name => $id_array )
{
if( $flag == 0 )
{
$where_connector = " WHERE ";
$flag = 1;
}
else
{
$where_connector = " AND ";
}
${ $field_name . '_string'} = join(',',${ $field_name . '_ids'});
$sql_append .= $where_connector . 'link_id IN ($ids)";
}
}
I'm self-taught and so constantly worry about best practices. I seem to remember some sort of function that handles arguments, perhaps in a way that can be applied here more efficiently. Any ideas?
To neatly construct your WHERE $fieldname IN($ids) clauses from a $fieldname=>$id_array array, you can try this :)
function buildWhereIns(array $args)
{
if(!is_array($args) || empty($args)) return "";
$ids = array_map(function($item){
return implode(',',$item);
}, array_values($args));
$fields = array_map(function($item,$id){
return $item.' IN('.$id.') ';
}, array_keys($args),$ids);
return = count($fields) > 0 ? count($fields) > 1 ? " WHERE " . implode(' AND ', $fields) : " WHERE " . array_shift($fields) : "";
}
I would say that the larger issue here is that you should be using some sort of technique to protect your code against SQL injection. For example, PHP's built-in PDO classes provide a really easy way to do this: http://www.php.net/manual/en/pdo.prepared-statements.php.
In general, though, if you want a loop that behaves differently on the first or last iteration, your method isn't bad. The other obvious method is to just do the first (or last) iteration outside the loop, and then perform the iterations which are the same inside the loop body.

How to convert SelectQuery object to SQL string?

I managed to print a string using __toString() magic method, but in this string I see placeholders (for conditions params), and it doesn't work as SQL query.
I checked documentation of this object, and also looked in google, but couldn't find a working answer.
Basing on question's comments (thanks #Scuzzy for inspiration) I wrote some simple piece of code to convert SelectQuery object:
class ExportableSelectQuery {
public static function toSql(SelectQuery $obj) {
$_string = $obj->__toString();
$_conditions = $obj->conditions();
$_tables = $obj->getTables();
$_fields = $obj->getFields();
foreach($_tables as $k => $t) {
if(!empty($t['alias'])) {
$_string = str_replace('{' . $t['table'] . '}', $t['table'] . ' as', $_string);
}
else {
$_string = str_replace('{' . $t['table'] . '}', $t['table'], $_string);
}
}
foreach($_conditions as $k => $c) {
if(is_int($c['value'])) {
$_string = str_replace(':db_condition_placeholder_' . $k, $c['value'], $_string);
}
else {
$_string = str_replace(':db_condition_placeholder_' . $k, "'" . $c['value'] . "'", $_string);
}
}
//echo('<pre>');
//var_dump($_fields);
//var_dump($_conditions);
//var_dump($_tables);
//var_dump($_string);
//echo('</pre>');
//die();
return $_string;
}
}
Usage of this code is now simple (if you only have SelectQuery object somewhere):
die(ExportableSelectQuery::toSql($query));
I was thinking about extending original SelectQuery object, and provide method to get SQL code, but Drupal's db_select function returns SelectQuery, so I will have to either change db_select function or cast returned object to ExportableSelectQuery.
Also this is not probably best solution I could write, but assuming limit of time and purpose it solved my problem just fine.
If you wish to get SQL from for example "EntityFieldQyery", you may use something like this
Add tag to query
$query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'page')
->addTag('EFQDumper'); //<=== TAG
Implement hook "query_TAG_alter"
function YOURMODULE_query_EFQDumper_alter(QueryAlterableInterface $query)
{
//echo ExportableSelectQuery::toSql($query);
//die();
}
The solution based on Carlos comment

mysql_real_escape_string not being used with given regex

I am using a dataHandler library to handle all of my db inserts / updates, etc.
The library has the following functions:
function prepareValue($value, $connection){
$preparedValue = $value;
if(is_null($value)){
$preparedValue = 'NULL';
}
else{
$preparedValue = '\''.mysql_real_escape_string($value, $connection).'\'';
}
return $preparedValue;
}
function parseParams($params, $type, $connection){
$fields = "";
$values = "";
if ($type == "UPDATE"){
$return = "";
foreach ($params as $key => $value){
if ($return == ""){
if (preg_match("/\)$/", $value)){
$return = $key."=".$value;
}
else{
$return = $key."=".$this->prepareValue($value, $connection);
}
}
else{
if (preg_match("/\)$/", $value)){
$return = $return.", ".$key."=".$value;
}
else{
$return = $return.", ".$key."=".$this->prepareValue($value,
$connection);
}
}
}
return $return;
/* rest of function contains similar but for "INSERT", etc.
}
These functions are then used to build queries using sprintf, as in:
$query = sprintf("UPDATE table SET " .
$this->parseParams($params, "UPDATE", $conn) .
" WHERE fieldValue = %s;", $this->prepareValue($thesis_id, $conn));
$params is an associative array: array("db_field_name"=>$value, "db_field_name2"=>$value2, etc.)
I am now running into problems when I want to do an update or insert of a string that ends in ")" because the parseParams function does not put these values in quotes.
My question is this:
Why would this library NOT call prepareValue on strings that end in a closed parenthesis? Would calling mysql_real_escape_string() on this value cause any problems? I could easily modify the library, but I am assuming there is a reason the author handled this particular regex this way. I just can't figure out what that reason is! And I'm hesitant to make any modifications until I understand the reasoning behind what is here.
Thanks for your help!
Please note that inside prepareValue not only mysql_real_escape_string is applied to the value but it is also put inside '. With this in mind, we could suspect that author assumed all strings ending with ) to be mysql function calls, ie:
$params = array(
'field1' => "John Doe",
'field2' => "CONCAT('John',' ','Doe')",
'field3' => "NOW()"
);
Thats the only reasonable answer that comes to mind.

Backticking MySQL Entities

I've the following method which allows me to protect MySQL entities:
public function Tick($string)
{
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
This works fairly well for the use that I make of it.
It protects database, table, field names and even the * operator, however now I also want it to protect function calls, ie:
AVG(database.employees.salary)
Should become:
AVG(`database`.`employees`.`salary`) and not `AVG(database`.`employees`.`salary)`
How should I go about this? Should I use regular expressions?
Also, how can I support more advanced stuff, from:
MAX(AVG(database.table.field1), MAX(database.table.field2))
To:
MAX(AVG(`database`.`table`.`field1`), MAX(`database`.`table`.`field2`))
Please keep in mind that I want to keep this method as simple/fast as possible, since it pretty much iterates over all the entity names in my database.
If this is quoting parts of an SQL statement, and they have only complexity that you descibe, a RegEx is a great approach. On the other hand, if you need to do this to full SQL statements, or simply more complicated components of statements (such as "MAX(AVG(val),MAX(val2))"), you will need to tokenize or parse the string and have a more sophisticated understanding of it to do this quoting accurately.
Given the regular expression approach, you may find it easier to break the function name out as one step, and then use your current code to quote the database/table/column names. This can be done in one RE, but it will be tricker to get right.
Either way, I'd highly recommend writing a few unit test cases. In fact, this is an ideal situation for this approach: it's easy to write the tests, you have some existing cases that work (which you don't want to break), and you have just one more case to add.
Your test can start as simply as:
assert '`ticked`' == Tick('ticked');
assert '`table`.`ticked`' == Tick('table.ticked');
assert 'db`.`table`.`ticked`' == Tick('db.table.ticked');
And then add:
assert 'FN(`ticked`)' == Tick('FN(ticked)');
etc.
Using the test case ndp gave I created a regex to do the hard work for you. The following regex will replace all word boundaries around words that are not followed by an opening parenthesis.
\b(\w+)\b(?!\()
The Tick() functionality would then be implemented in PHP as follows:
function Tick($string)
{
return preg_replace( '/\b(\w+)\b(?!\()/', '`\1`', $string );
}
It's generally a bad idea to pass the whole SQL to the function. That way, you'll always find a case when it doesn't work, unless you fully parse the SQL syntax.
Put the ticks to the names on some previous abstraction level, which makes up the SQL.
Before you explode your string on periods, check if the last character is a parenthesis. If so, this call is a function.
<?php
$string = str_replace('`', '', $string)
$function = "";
if (substr($string,-1) == ")") {
// Strip off function call first
$opening = strpos($string, "(");
$function = substr($string, 0, $opening+1);
$string = substr($string, $opening+1, -1);
}
// Do your existing parsing to $string
if ($function == "") {
// Put function back on string
$string = $function . $string . ")";
}
?>
If you need to cover more advanced situations, like using nested functions, or multiple functions in sequence in one "$string" variable, this would become a much more advanced function, and you'd best ask yourself why these elements aren't being properly ticked in the first place, and not need any further parsing.
EDIT: Updating for nested functions, as per original post edit
To have the above function deal with multiple nested functions, you likely need something that will 'unwrap' your nested functions. I haven't tested this, but the following function might get you on the right track.
<?php
function unwrap($str) {
$pos = strpos($str, "(");
if ($pos === false) return $str; // There's no function call here
$last_close = 0;
$cur_offset = 0; // Start at the beginning
while ($cur_offset <= strlen($str)) {
$first_close = strpos($str, ")", $offset); // Find first deep function
$pos = strrpos($str, "(", $first_close-1); // Find associated opening
if ($pos > $last_close) {
// This function is entirely after the previous function
$ticked = Tick(substr($str, $pos+1, $first_close-$pos)); // Tick the string inside
$str = substr($str, 0, $pos)."{".$ticked."}".substr($str,$first_close); // Replace parenthesis by curly braces temporarily
$first_close += strlen($ticked)-($first_close-$pos); // Shift parenthesis location due to new ticks being added
} else {
// This function wraps other functions; don't tick it
$str = substr($str, 0, $pos)."{".substr($str,$pos+1, $first_close-$pos)."}".substr($str,$first_close);
}
$last_close = $first_close;
$offset = $first_close+1;
}
// Replace the curly braces with parenthesis again
$str = str_replace(array("{","}"), array("(",")"), $str);
}
If you are adding the function calls in your code, as opposed to passing them in through a string-only interface, you can replace the string parsing with type checking:
function Tick($value) {
if (is_object($value)) {
$result = $value->value;
} else {
$result = '`'.str_replace(array('`', '.'), array('', '`.`'), $value).'`';
}
return $result;
}
class SqlFunction {
var $value;
function SqlFunction($function, $params) {
$sane = implode(', ', array_map('Tick', $params));
$this->value = "$function($sane)";
}
}
function Maximum($column) {
return new SqlFunction('MAX', array($column));
}
function Avg($column) {
return new SqlFunction('AVG', array($column));
}
function Greatest() {
$params = func_get_args();
return new SqlFunction('GREATEST', $params);
}
$cases = array(
"'simple'" => Tick('simple'),
"'table.field'" => Tick('table.field'),
"'table.*'" => Tick('table.*'),
"'evil`hack'" => Tick('evil`hack'),
"Avg('database.table.field')" => Tick(Avg('database.table.field')),
"Greatest(Avg('table.field1'), Maximum('table.field2'))" => Tick(Greatest(Avg('table.field1'), Maximum('table.field2'))),
);
echo "<table>";
foreach ($cases as $case => $result) {
echo "<tr><td>$case</td><td>$result</td></tr>";
}
echo "</table>";
This avoids any possible SQL injection while remaining legible to future readers of your code.
You could use preg_replace_callback() in conjunction with your Tick() method to skip at least one level of parens:
public function tick($str)
{
return preg_replace_callback('/[^()]*/', array($this, '_tick_replace_callback'), $str);
}
protected function _tick_replace_callback($str) {
$string = explode('.', str_replace('`', '', $string));
foreach ($string as $key => $value)
{
if ($value != '*')
{
$string[$key] = '`' . trim($value) . '`';
}
}
return implode('.', $string);
}
Are you generating the SQL Query or is it being passed to you? If you generating the query I wouldn't pass the whole query string just the parms/values you want to wrap in the backticks or what ever else you need.
EXAMPLE:
function addTick($var) {
return '`' . $var . '`';
}
$condition = addTick($condition);
$SQL = 'SELECT' . $what . '
FROM ' . $table . '
WHERE ' . $condition . ' = ' . $constraint;
This is just a mock but you get the idea that you can pass or loop through your code and build the query string rather than parsing the query string and adding your backticks.

Categories