Class attributes always set as strings? - php

First time I noticed this today whilst using a PHP class static attribute $limit as a query limit parameter for a raw prepared statement as in
/**
* #var int
*/
private static $limit = 100;
// ....
$query = <<<EOT
SELECT id FROM table t WHERE t.id > 0 LIMIT ? EOT;
$recs = \DB::select($query, [self::$limit]);
So the above query throws an error which upon examination seemed to me to stem from the fact that the query builder took it for a string. If I change the select statement to be as follows it all runs as expected
$mcdrs = \DB::select($query, [(int)self::$limit]);
So, whilst this is not a problem per se, I am just curious as to whether is a known fact that class attributes in PHP are always set as strings.
I even changed the attribute declaration to
private static $limit = 99+1;
with the same results. Just to confirm, I performed the following "tests" and I can confirm that no matter what I do with the static var declaration they are all reported as strings. Haven't tried floating numbers though.
if (is_string(self::$limit)) {
die('string');
} elseif (is_int(self::$limit)) {
die('int');
} else {
die('something else...');
}
I can confirm that the variable is of type string.

I came up with this as a test and could not replicate your findings.
This is using PHP 7.3.19
/Class_test.php
class Class_test {
private static $limit = 100;
private static $string = '100';
public function index() {
$this->check_my_type(self::$limit);
$this->check_my_type(self::$string);
}
protected function check_my_type($value){
if (is_string($value)) {
echo('I am a string :'. $value);
echo '<br>';
}
if (is_int($value)) {
echo('I am an Integer :'.$value);
echo '<br>';
}
}
}
/index.php
<?php
// Enable FULL Error Reporting on the screen
// ONLY USE IN DEVELOPMENT
error_reporting(E_ALL);
ini_set('display_errors', 1);
include_once('./Class_test.php');
$test = new Class_test();
$test->index();
Output is:
I am an Integer :100
I am a string :100
Do you get the same results or is there something you haven't told us that I am missing here.

Related

How should I return an error (and a message) in large project?

I'm writing a large project, and here's a class that I'll use it often:
class Star
{
/**
* Add
*
* Add a star to something.
*
* #param int $ID The ID of the thing.
*/
function Add($ID)
{
if($this->Starred($ID))
return 'You starred it already.';
if(!$this->Existing($ID))
return 'The one you tried to star does no longer exist.';
$this->DB->Star($ID);
return 'Starred successfully!';
}
}
$Star = new Star();
But I will use it in different ways like: single page or inside a function,
here's the problem, sometimes, I want to know the return code not the message,
but when I use it in the single page, I want it to return the messaage,
so if I change the Add() function to this:
function Add($ID)
{
if($this->Starred($ID))
return 0;
if(!$this->Existing($ID))
return 1;
$this->DB->Star($ID);
return 2;
}
I can now use it in my functions like this to handle an error:
/** Leaves a comment */
$Comment->Say('Hello.', $ID);
/** Auto star the post because we commented on it */
if($Star->Add($ID) == 2)
{
/** Remove the comment because the post does no longer exist */
$Comment->Remove('Hello.', $ID);
return 'Sorry ; _ ;, the post does no longer exist.';
}
but what if I need to return a message in many other pages?
I need to write this code every time?
switch($Star->Add($ID))
{
case 0:
return 'You starred it already.';
break;
case 1:
return 'The one you tried to star does no longer exist.';
break;
case 2:
return 'Starred successfully!';
break;
}
I'm just confuse about it, any help would be appreciated.
For a direct solution to your code read the Edit 1 section.
I'm currently working on a rather large project and I'm using a ErrorHandler class that I made. I found that working with a generic error handler class has made it easier.
class ErrorHandler
{
/**
* #var string an array containing all the errors set.
*/
private static $errors = [];
/**
* Set an error.
*
* #param string $error - The error message you'd like to set.
* #return string - The error being set to $errors array.
*/
public static function add($error)
{
return self::$errors[] = $error;
}
/**
* Get all the errors.
*
* #return boolean if the $errors array is empty it will return false, otherwise it will return the errors.
*/
public static function get()
{
foreach (self::$errors as $error) {
if (empty(trim($error)))
return false;
}
return self::$errors;
}
}
Basically how I use it is like this, say I needed to validate a form input say a login, I'd first check if the user pressed the submit button, then I'd use the ternary operator to run some validations and if it fails I use the ErrorHandler class.
if(isset($_POST['login'])) {
$emailAddress = someValidationsHere ? doSomethingWithValidInput : ErrorHandler::add("Email field is empty or format is invalid.");
$password = someValidationsHere ? doSomethingWithValidInput : ErrorHandler::add("Password field can't be empty and can't use special characters.");
if(!ErrorHandler::get()) {
echo User::login($emailAddress, $password, $autoLogin);
} else {
$errors = ErrorHandler::get();
foreach($errors as $error) {
echo $error . "<br/>";
}
}
}
So what that bottom if statement does is check if the ErrorHandler::get functions does not return false which in that case no need to show an error message and you can progress with the code, else it will display the error page, this way you can show multiple errors and have custom formatting.
I prefer this method as it is more of a long term solution as you may change the ID's then you'd have to go through all your code and change the code manually. Also it gives your code some sort of structure and that keeps your code clean.
Edit 1
How is this class? You now know the error codes using the const value and you can parse the error code to a message using the getMessage function. Also your code is more understandable and adaptable.
Why is it more...
understandable?
Because now when you (or someone else) looks at this code they see the clean name from the const so ALREADY_STARRED_ERROR will let the developer know instantly what the error means.
adaptable?
Well now you can change your hard coded errors and it wouldn't affect the code in anyway, so if in the future you wish to changed it because of a spelling mistake or other errors, you can change the array message.
<?php
class Star
{
const ALREADY_STARRED_ERROR = 1;
const NOT_FOUND_ERROR= 2;
const SUCCESSFUL_ENTRY = 3;
function getMessage($code)
{
$messages = [
1 => "You starred it already.",
2 => "The one you tried to star does no longer exist.",
3 => "Starred successfully!"
];
return $message[$code];
}
/**
* Add
*
* Add a star to something.
*
* #param int $ID The ID of the thing.
*/
function Add($ID)
{
if($this->Starred($ID))
return self::ALREADY_STARRED_ERROR;
if(!$this->Existing($ID))
return self::NOT_FOUND_ERROR;
$this->DB->Star($ID);
return self::SUCCESSFUL_ENTRY;
}
}
?>
I'd like to think Edit 1 addressed both the issues you had.
sometimes, I want to know the return code not the message,
but when I use it in the single page, I want it to return the messaage,
Place the switch to a function, like AddWithMessage:
function AddWithMessage($Star)
{
switch($Star->Add($ID))
{
case 0:
return 'You starred it already.';
break;
case 1:
return 'The one you tried to star does no longer exist.';
break;
case 2:
return 'Starred successfully!';
break;
}
}
Then use it across any single page you need instead of Add

joomla 2.5 pagination always set start limit to 20

I developed my own joomla 2.5 custom component for displaying data table in front-end.It contain filtering,paging and sorting.When navigate via paging it always shows only first 20.
Is there any way to override limit of a query which generate on function getListQuery().
My populateState method is
protected function populateState($ordering = null, $direction = null) {
// Initialise variables.
$app = JFactory::getApplication();
$search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
$filter_order = $this->getUserStateFromRequest($this->context . '.filter_order', 'filter_order');
//$filter_order = JRequest::getCmd('filter_order');
$filter_order_Dir = $this->getUserStateFromRequest($this->context . '.filter_order_Dir', 'filter_order_Dir');
//$filter_order_Dir = JRequest::getCmd('filter_order_Dir');
'filter_region', '');
$this->setState('filter_order', $filter_order);
$this->setState('filter_order_Dir', $filter_order_Dir);
// List state information
$limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->getCfg('list_limit'));
$this->setState('list.limit', $limit);
$limitstart = JRequest::getVar('limitstart', 0, '', 'int');
$this->setState('list.start', $limitstart);
parent::populateState();
}
Constructor method is
function __construct() {
parent::__construct();
//Get configuration
$app = JFactory::getApplication();
$config = JFactory::getConfig();
// Get the pagination request variables
$this->setState('limit', $app->getUserStateFromRequest('com_jointcm.limit', 'limit', $config->getValue('config.list_limit'), 'int'));
$this->setState('limitstart', JRequest::getVar('limitstart', 0, '', 'int'));
}
List query method is
protected function getListQuery() {
// Create a new query object.
$db = JFactory::getDBO();
$query = $db->getQuery(true);
//code goes here...
..............
return $query;
}
After some digging around and taking a look at the source code of the JModelList class, I realized that problem is with
\libraries\joomla\application\component\modellist.php file ,method name public function getItems(),line number 115.
I changed it to
public function getItems()
{
// Get a storage key.
$store = $this->getStoreId();
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Load the list items.
$query = $this->_getListQuery();
//$items = $this->_getList($query, $this->getStart(), $this->getState('list.limit'));
$items = $this->_getList($query, $this->getState('limitstart'), $this->getState('list.limit'));
// Check for a database error.
if ($this->_db->getErrorNum())
{
$this->setError($this->_db->getErrorMsg());
return false;
}
// Add the items to the internal cache.
$this->cache[$store] = $items;
return $this->cache[$store];
}
Change was
$items = $this->_getList($query, $this->getStart(), $this->getState('list.limit'));
to
$items = $this->_getList($query, $this->getState('limitstart'), $this->getState('list.limit'));
It works fine.
In JModelList's getItems() the default method uses getStart() which in turn uses your models getQuery() to get a count of the number of items returned by your query, via _getListCount($query) which in turn calls the particular database adaptors version of getNumRows()). That value is used in the calculation in getStart(), if you have a large complicated query and don't really need to use the fancy getStart() implementation you can just override it in your model (i.e. your version of the JModelList class)
e.g. for our components model's for the front end which have rather complicated $query's returned by getListQuery, in their most basic implementation they do something similar to this:
public function getStart()
{
return $this->getState('list.start');
}
If you don't override it the default JModelList getStart() is invoked which looks like this:
/**
* Method to get the starting number of items for the data set.
*
* #return integer The starting number of items available in the data set.
*
* #since 11.1
*/
public function getStart()
{
$store = $this->getStoreId('getstart');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
$start = $this->getState('list.start');
$limit = $this->getState('list.limit');
$total = $this->getTotal();
if ($start > $total - $limit)
{
$start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
}
// Add the total to the internal cache.
$this->cache[$store] = $start;
return $this->cache[$store];
}
But, this probably isn't the problem area, it's more likely in your populateState(). At the end of populateState() you call parent::populateState() (if was called at the beginning it wouldn't be overwriting results of your method).
You seem to be duplicating the work done by the parent::populateState() which is probably redundant, looking at JModelList's implementation you will see this:
protected function populateState($ordering = null, $direction = null)
{
// If the context is set, assume that stateful lists are used.
if ($this->context)
{
$app = JFactory::getApplication();
$value = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->getCfg('list_limit'), 'uint');
$limit = $value;
$this->setState('list.limit', $limit);
$value = $app->getUserStateFromRequest($this->context . '.limitstart', 'limitstart', 0);
$limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0);
$this->setState('list.start', $limitstart);
// Check if the ordering field is in the white list, otherwise use the incoming value.
$value = $app->getUserStateFromRequest($this->context . '.ordercol', 'filter_order', $ordering);
if (!in_array($value, $this->filter_fields))
{
$value = $ordering;
$app->setUserState($this->context . '.ordercol', $value);
}
$this->setState('list.ordering', $value);
// Check if the ordering direction is valid, otherwise use the incoming value.
$value = $app->getUserStateFromRequest($this->context . '.orderdirn', 'filter_order_Dir', $direction);
if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
{
$value = $direction;
$app->setUserState($this->context . '.orderdirn', $value);
}
$this->setState('list.direction', $value);
}
else
{
$this->setState('list.start', 0);
$this->state->set('list.limit', 0);
}
}
The most obvious condition in the parent::populateState() that causes list.start to be set to 0 is the very first line, which checks your object context value, it may be that something is going wrong there and your objects context value is equating to false. (I can't see context defined anywhere... so, it will try an guess/build a context value for you in __construct()).
However, it may also be in the way in which getUserSateFromRequest() is processing the values returned from your request, it's hard to tell with the code available.
You can add limit like this $query->limit('0,40'); in getListQuery() function
Do you have list_limit defined in your component options? If not, then add a new parameter to your component options and call it list_limit. This will allow you to set your pagination limit to what ever you want in the component options.

How to control scoring and ordering in Zend_Search_Lucene, so one field is more important than the other?

From what I understand, after reading documentation (especially scoring part), every field I add has the same level of importance when scoring searched results. I have following code:
protected static $_indexPath = 'tmp/search/indexes/projects';
public static function createSearchIndex()
{
$_index = new Zend_Search_Lucene(APPLICATION_PATH . self::$_indexPath, true);
$_projects_stmt = self::getProjectsStatement();
$_count = 0;
while ($row = $_projects_stmt->fetch()) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::text('name', $row['name']));
$doc->addField(Zend_Search_Lucene_Field::text('description', $row['description']));
$doc->addField(Zend_Search_Lucene_Field::unIndexed('projectId', $row['id']));
$_index->addDocument($doc);
}
$_index->optimize();
$_index->commit();
}
The code is simple - I'm generating index, based on data fetched from db, and save it in the specified location.
I was looking in many places, as my desired behavior is that name field is more important than description (let's say 75% and 25%). So when I will search for some phrase, and it will be found in description of the first document, and in name of the second document, then second document will in fact have 3 times bigger score, and will show up higher on my list.
Is there any way to control scoring/ordering in this way?
I found it out basing on this documentation page. You need to create new Similarity algorithm class, and overwrite lengthNorm method. I copied this method from Default class, added $multiplier variable, and set it's value when needed (for a column I want):
class Zend_Search_Lucene_Search_Similarity_Projects extends Zend_Search_Lucene_Search_Similarity_Default
{
/**
* #param string $fieldName
* #param integer $numTerms
* #return float
*/
public function lengthNorm($fieldName, $numTerms)
{
if ($numTerms == 0) {
return 1E10;
}
$multiplier = 1;
if($fieldName == 'name') {
$multiplier = 3;
}
return 1.0/sqrt($numTerms / $multiplier);
}
}
Then the only thing you need to do (edit of code from question) is set your new Similarity algorithm class as a default method just before indexing:
protected static $_indexPath = 'tmp/search/indexes/projects';
public static function createSearchIndex()
{
Zend_Search_Lucene_Search_Similarity::setDefault(new Zend_Search_Lucene_Search_Similarity_Projects());
$_index = new Zend_Search_Lucene(APPLICATION_PATH . self::$_indexPath, true);
$_projects_stmt = self::getProjectsStatement();
$_count = 0;
while ($row = $_projects_stmt->fetch()) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::text('name', $row['name']));
$doc->addField(Zend_Search_Lucene_Field::text('description', $row['description']));
$doc->addField(Zend_Search_Lucene_Field::unIndexed('projectId', $row['id']));
$_index->addDocument($doc);
}
$_index->optimize();
$_index->commit();
}
I wanted to extra boost name field, but you can do it with anyone.

recursive function in php class

I want to create a function in class, to create username, function will check if username exist then it will increment username like username_1. and check if this username exist or not if it exist again increment it to username_2 till new username created. I have created this function but it return me nothing.Please help me what is wrong in my code.
class a{
function check_username($username){
if($usernameexist){
return true;
}
else
{
return false;
}
}
function create_username($username) {
$__name = __FUNCTION__;
if ($this->check_username($username)) {
$n++;
$username = $username . "_" . $n;
//return $__name($username); this return fatal error.
return call_user_func('create_username', $username);
} else {
return $username;
}
}
}
No need to use recursion for this, a simple while(){} loop will do:
Plain-Jane Interator method
// your original function
function create_username($username){
// check if the username (as-is) already exists
if ($this->check_username($username)){
// use $n to keep a counter
$n = 1;
// while {username}_{n} exists, keep incrementing the counter
while ($this->check_username($username.'_'.$n)){
$n++;
/* If you don't want this to check to infinity, uncomment
* the below portion. the 100 is an arbitrary number, but use
* whatever you want as a limitation (could even make it a
* parameter in the method). Also, returning FALSE allows you to
* gracefully catch when max attempts are reached.
*
* e.g.
* if (($new_user = $obj->create_username('BradChristie')) !== FALSE){
* // user was successfully created within the max allowed attempts
* }
*/
//if ($n > 100) return FALSE
}
// return the result
return $username.'_'.$n;
}
// username was fine, return it back
return $username;
}
Recursive method
// recursive username check
public function create_username($username, $n = 0)
{
/* Same as above function, this is a check to prevent counting
* to infinity. uncomment to apply it
*/
//if ($n > 100) return FALSE;
// establish the username we're testing. if $n is 0,
// it's the original call to the function (don't add _0)
// if it's >0, it's part of the search so include it
$_username = $username . ($n > 0 ? '_'.$n : '');
// check if the username exists.
if ($this->check_username($_username))
{
// it exists, so make a call to this same function passing
// the original username and the value of n + 1 (move to next
// possibility)
return $this->create_username($username, $n+1);
}
// the name, as-is, was fine. return it
return $_username;
}
Example
Your code is wrong in several ways and, as pointed out elsewhere, your desired function is better written iteratively.
Some of the problems with your code are as follows:
You are doing your recursive check when check_username has succeeded. So, if you fail to find the original $username you are never modifying it, so never checking the modified value.
You are modifying the name passed to create_username by appending _n (for appropriate n). Since you are passing a modified name in your recursive call you will actually end up with multiple _n parts on the name.
Since you are not limiting your recursive calls, even if this was written correctly, you would eventually get nested too deep.
There is no need for recursivity in this case... A simple loop would do just perfectly:
function create_username($username) {
$original_username = $username;
$i=1;
while(! $this->check_username($username) ) {
$username = $original_username . '_' .$i++;
}
return $username;
}

Doctrine - How to print out the real sql, not just the prepared statement?

We're using Doctrine, a PHP ORM. I am creating a query like this:
$q = Doctrine_Query::create()->select('id')->from('MyTable');
and then in the function I'm adding in various where clauses and things as appropriate, like this
$q->where('normalisedname = ? OR name = ?', array($string, $originalString));
Later on, before execute()-ing that query object, I want to print out the raw SQL in order to examine it, and do this:
$q->getSQLQuery();
However that only prints out the prepared statement, not the full query. I want to see what it is sending to the MySQL, but instead it is printing out a prepared statement, including ?'s. Is there some way to see the 'full' query?
Doctrine is not sending a "real SQL query" to the database server : it is actually using prepared statements, which means :
Sending the statement, for it to be prepared (this is what is returned by $query->getSql())
And, then, sending the parameters (returned by $query->getParameters())
and executing the prepared statements
This means there is never a "real" SQL query on the PHP side — so, Doctrine cannot display it.
A working example:
$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL:
echo $query->getSQL();
// Show Parameters:
echo $query->getParameters();
You can check the query executed by your app if you log all the queries in mysql:
http://dev.mysql.com/doc/refman/5.1/en/query-log.html
there will be more queries not only the one that you are looking for but you can grep for it.
but usually ->getSql(); works
Edit:
to view all the mysql queries I use
sudo vim /etc/mysql/my.cnf
and add those 2 lines:
general_log = on
general_log_file = /tmp/mysql.log
and restart mysql
Edit 2
In case you dont find the mysql config (it can be in many places), just set those variables from mysql command line.
mysql -u root -p
SHOW VARIABLES LIKE 'general_log_file';
SHOW VARIABLES LIKE 'general_log';
SET GLOBAL general_log = 'on';
SET GLOBAL general_log_file = '/tmp/mysql.log';
//view the queries
sudo tail -f /tmp/mysql.log
The life of those settings is until MySQL is restarted. Or the laptop. So they are not permanent - which is great in my opinion - I just need them when I debug and I dont need to worry to edit the config then to remove them. If you dont remove the logging, it might grow too much if you forget about it.
I have created a Doctrine2 Logger that does exactly this. It "hydrates" the parametrized sql query with the values using Doctrine 2 own data type conversors.
<?php
namespace Drsm\Doctrine\DBAL\Logging;
use Doctrine\DBAL\Logging\SQLLogger,
Doctrine\DBAL\Types\Type,
Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* A SQL logger that logs to the standard output and
* subtitutes params to get a ready to execute SQL sentence
* #author dsamblas#gmail.com
*/
class EchoWriteSQLWithoutParamsLogger implements SQLLogger
{
const QUERY_TYPE_SELECT="SELECT";
const QUERY_TYPE_UPDATE="UPDATE";
const QUERY_TYPE_INSERT="INSERT";
const QUERY_TYPE_DELETE="DELETE";
const QUERY_TYPE_CREATE="CREATE";
const QUERY_TYPE_ALTER="ALTER";
private $dbPlatform;
private $loggedQueryTypes;
public function __construct(AbstractPlatform $dbPlatform, array $loggedQueryTypes=array()){
$this->dbPlatform=$dbPlatform;
$this->loggedQueryTypes=$loggedQueryTypes;
}
/**
* {#inheritdoc}
*/
public function startQuery($sql, array $params = null, array $types = null)
{
if($this->isLoggable($sql)){
if(!empty($params)){
foreach ($params as $key=>$param) {
$type=Type::getType($types[$key]);
$value=$type->convertToDatabaseValue($param,$this->dbPlatform);
$sql = join(var_export($value, true), explode('?', $sql, 2));
}
}
echo $sql . " ;".PHP_EOL;
}
}
/**
* {#inheritdoc}
*/
public function stopQuery()
{
}
private function isLoggable($sql){
if (empty($this->loggedQueryTypes)) return true;
foreach($this->loggedQueryTypes as $validType){
if (strpos($sql, $validType) === 0) return true;
}
return false;
}
}
Usage Example:;
The following peace of code will echo on standard output any INSERT,UPDATE,DELETE SQL sentences generated with $em Entity Manager,
/**#var \Doctrine\ORM\EntityManager $em */
$em->getConnection()
->getConfiguration()
->setSQLLogger(
new EchoWriteSQLWithoutParamsLogger(
$em->getConnection()->getDatabasePlatform(),
array(
EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_UPDATE,
EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_INSERT,
EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_DELETE
)
)
);
getSqlQuery() does technically show the whole SQL command, but it's a lot more useful when you can see the parameters as well.
echo $q->getSqlQuery();
foreach ($q->getFlattenedParams() as $index => $param)
echo "$index => $param";
To make this pattern more reusable, there's a nice approach described in the comments at Raw SQL from Doctrine Query Object.
There is no other real query, this is how prepared statements work. The values are bound in the database server, not in the application layer.
See my answer to this question: In PHP with PDO, how to check the final SQL parametrized query?
(Repeated here for convenience:)
Using prepared statements with parametrised values is not simply another way to dynamically create a string of SQL. You create a prepared statement at the database, and then send the parameter values alone.
So what is probably sent to the database will be a PREPARE ..., then SET ... and finally EXECUTE ....
You won't be able to get some SQL string like SELECT * FROM ..., even if it would produce equivalent results, because no such query was ever actually sent to the database.
You can easily access the SQL parameters using the following approach.
$result = $qb->getQuery()->getSQL();
$param_values = '';
$col_names = '';
foreach ($result->getParameters() as $index => $param){
$param_values .= $param->getValue().',';
$col_names .= $param->getName().',';
}
//echo rtrim($param_values,',');
//echo rtrim($col_names,',');
So if you printed out the $param_values and $col_names , you can get the parameter values passing through the sql and respective column names.
Note : If $param returns an array, you need to re iterate, as parameters inside IN (:?) usually comes is as a nested array.
Meantime if you found another approach, please be kind enough to share with us :)
Thank you!
My solution:
/**
* Get SQL from query
*
* #author Yosef Kaminskyi
* #param QueryBilderDql $query
* #return int
*/
public function getFullSQL($query)
{
$sql = $query->getSql();
$paramsList = $this->getListParamsByDql($query->getDql());
$paramsArr =$this->getParamsArray($query->getParameters());
$fullSql='';
for($i=0;$i<strlen($sql);$i++){
if($sql[$i]=='?'){
$nameParam=array_shift($paramsList);
if(is_string ($paramsArr[$nameParam])){
$fullSql.= '"'.addslashes($paramsArr[$nameParam]).'"';
}
elseif(is_array($paramsArr[$nameParam])){
$sqlArr='';
foreach ($paramsArr[$nameParam] as $var){
if(!empty($sqlArr))
$sqlArr.=',';
if(is_string($var)){
$sqlArr.='"'.addslashes($var).'"';
}else
$sqlArr.=$var;
}
$fullSql.=$sqlArr;
}elseif(is_object($paramsArr[$nameParam])){
switch(get_class($paramsArr[$nameParam])){
case 'DateTime':
$fullSql.= "'".$paramsArr[$nameParam]->format('Y-m-d H:i:s')."'";
break;
default:
$fullSql.= $paramsArr[$nameParam]->getId();
}
}
else
$fullSql.= $paramsArr[$nameParam];
} else {
$fullSql.=$sql[$i];
}
}
return $fullSql;
}
/**
* Get query params list
*
* #author Yosef Kaminskyi <yosefk#spotoption.com>
* #param Doctrine\ORM\Query\Parameter $paramObj
* #return int
*/
protected function getParamsArray($paramObj)
{
$parameters=array();
foreach ($paramObj as $val){
/* #var $val Doctrine\ORM\Query\Parameter */
$parameters[$val->getName()]=$val->getValue();
}
return $parameters;
}
public function getListParamsByDql($dql)
{
$parsedDql = preg_split("/:/", $dql);
$length = count($parsedDql);
$parmeters = array();
for($i=1;$i<$length;$i++){
if(ctype_alpha($parsedDql[$i][0])){
$param = (preg_split("/[' ' )]/", $parsedDql[$i]));
$parmeters[] = $param[0];
}
}
return $parmeters;}
Example of usage:
$query = $this->_entityRepository->createQueryBuilder('item');
$query->leftJoin('item.receptionUser','users');
$query->where('item.customerid = :customer')->setParameter('customer',$customer)
->andWhere('item.paymentmethod = :paymethod')->setParameter('paymethod',"Bonus");
echo $this->getFullSQL($query->getQuery());
More clear solution:
/**
* Get string query
*
* #param Doctrine_Query $query
* #return string
*/
public function getDqlWithParams(Doctrine_Query $query){
$vals = $query->getFlattenedParams();
$sql = $query->getDql();
$sql = str_replace('?', '%s', $sql);
return vsprintf($sql, $vals);
}
You can use :
$query->getSQL();
If you are using MySQL you can use Workbench to view running SQL statements.
You can also use view the running query from mysql by using the following :
SHOW FULL PROCESSLIST \G
Solution:1
====================================================================================
function showQuery($query)
{
return sprintf(str_replace('?', '%s', $query->getSql()), $query->getParams());
}
// call function
echo showQuery($doctrineQuery);
Solution:2
====================================================================================
function showQuery($query)
{
// define vars
$output = NULL;
$out_query = $query->getSql();
$out_param = $query->getParams();
// replace params
for($i=0; $i<strlen($out_query); $i++) {
$output .= ( strpos($out_query[$i], '?') !== FALSE ) ? "'" .str_replace('?', array_shift($out_param), $out_query[$i]). "'" : $out_query[$i];
}
// output
return sprintf("%s", $output);
}
// call function
echo showQuery($doctrineQueryObject);
TL;DR
$qb = ... // your query builder
$query = $qb->getQuery();
// temporarily enable logging for your query (will also work in prod env)
$conf = $query->getEntityManager()->getConnection()->getConfiguration();
$backupLogger = $conf->getSQLLogger();
$logger = new \Doctrine\DBAL\Logging\DebugStack();
$conf->setSQLLogger($logger);
// execute query
$res = $query->getResult();
$conf->setSQLLogger($backupLogger); //restore logger for other queries
$params = [
'query' => array_pop($logger->queries) //extract query log details
//your other twig params here...
]
return $params; //send this to your twig template...
in your twig files, use Doctrine's twig helpers filters:
// show raw query:
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)
// highlighted
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query(highlight_only = true) }}
// highlighted and formatted (i.e. with tabs and newlines)
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query }}
Explanation:
The other answers mentioning that Prepared statement are actually "real queries" are right, but they don't answer the obvious asker's expectation... Every developer wants to display a "runnable query" for debugging (or to display it to the user).
So, I looked into Symfony profiler's source to see how they do it. The Doctrine part is Doctrine's responsibility so they made a doctrine-bundle to integrate with Symfony. Having a look at the doctrine-bundle/Resources/views/Collector/db.html.twig file, you will find out how they do it (this might change across versions). Interestingly, they created twig filters that we can reuse (see above).
For everything to work we need to enable Logging for our query. There are multiple ways to do this and here I use DebugStack which allows to log queries without actually printing them. This also ensure that this will work in production mode if this is what you need...
If you need further formatting, you will see that they include some CSS in a style tag, so simply "steal" it ^^:
.highlight pre { margin: 0; white-space: pre-wrap; }
.highlight .keyword { color: #8959A8; font-weight: bold; }
.highlight .word { color: #222222; }
.highlight .variable { color: #916319; }
.highlight .symbol { color: #222222; }
.highlight .comment { color: #999999; }
.highlight .backtick { color: #718C00; }
.highlight .string { color: #718C00; }
.highlight .number { color: #F5871F; font-weight: bold; }
.highlight .error { color: #C82829; }
Hope, this will help ;-)
Maybe it can be useful for someone:
// Printing the SQL with real values
$vals = $query->getFlattenedParams();
foreach(explode('?', $query->getSqlQuery()) as $i => $part) {
$sql = (isset($sql) ? $sql : null) . $part;
if (isset($vals[$i])) $sql .= $vals[$i];
}
echo $sql;
I wrote a simple logger, which can log query with inserted parameters.
Installation:
composer require cmyker/doctrine-sql-logger:dev-master
Usage:
$connection = $this->getEntityManager()->getConnection();
$logger = new \Cmyker\DoctrineSqlLogger\Logger($connection);
$connection->getConfiguration()->setSQLLogger($logger);
//some query here
echo $logger->lastQuery;
I made some research for this topic, because i wanted to debug a generated SQL query and execute it in the sql editor. As seen in all the answers, it is a highly technical topic.
When i assume that the initial question is base on dev-env, one very simple answer is missing at the moment. You can just use the build in Symfony profiler. Just click on the Doctrine Tab, Scroll to the query you want to inspect. Then click on "view runnable query" and you can paste your query directly in your SQL editor
More UI base approach but very quick and without debugging code overhead.
$sql = $query->getSQL();
$parameters = [];
foreach ($query->getParameters() as $parameter) {
$parameters[] = $parameter->getValue();
}
$result = $connection->executeQuery($sql, $parameters)
->fetchAll();
Modified #dsamblas function to work when parameters are date strings like this '2019-01-01' and when there is array passed using IN like
$qb->expr()->in('ps.code', ':activeCodes'),
. So do everything what dsamblas wrote, but replace startQuery with this one or see the differences and add my code. (in case he modified something in his function and my version does not have modifications).
public function startQuery($sql, array $params = null, array $types = null)
{
if($this->isLoggable($sql)){
if(!empty($params)){
foreach ($params as $key=>$param) {
try {
$type=Type::getType($types[$key]);
$value=$type->convertToDatabaseValue($param,$this->dbPlatform);
} catch (Exception $e) {
if (is_array($param)) {
// connect arrays like ("A", "R", "C") for SQL IN
$value = '"' . implode('","', $param) . '"';
} else {
$value = $param; // case when there are date strings
}
}
$sql = join(var_export($value, true), explode('?', $sql, 2));
}
}
echo $sql . " ;".PHP_EOL;
}
}
Did not test much.
$sql = $query->getSQL();
$obj->mapDQLParametersNamesToSQL($query->getDQL(), $sql);
echo $sql;//to see parameters names in sql
$obj->mapDQLParametersValuesToSQL($query->getParameters(), $sql);
echo $sql;//to see parameters values in sql
public function mapDQLParametersNamesToSQL($dql, &$sql)
{
$matches = [];
$parameterNamePattern = '/:\w+/';
/** Found parameter names in DQL */
preg_match_all($parameterNamePattern, $dql, $matches);
if (empty($matches[0])) {
return;
}
$needle = '?';
foreach ($matches[0] as $match) {
$strPos = strpos($sql, $needle);
if ($strPos !== false) {
/** Paste parameter names in SQL */
$sql = substr_replace($sql, $match, $strPos, strlen($needle));
}
}
}
public function mapDQLParametersValuesToSQL($parameters, &$sql)
{
$matches = [];
$parameterNamePattern = '/:\w+/';
/** Found parameter names in SQL */
preg_match_all($parameterNamePattern, $sql, $matches);
if (empty($matches[0])) {
return;
}
foreach ($matches[0] as $parameterName) {
$strPos = strpos($sql, $parameterName);
if ($strPos !== false) {
foreach ($parameters as $parameter) {
/** #var \Doctrine\ORM\Query\Parameter $parameter */
if ($parameterName !== ':' . $parameter->getName()) {
continue;
}
$parameterValue = $parameter->getValue();
if (is_string($parameterValue)) {
$parameterValue = "'$parameterValue'";
}
if (is_array($parameterValue)) {
foreach ($parameterValue as $key => $value) {
if (is_string($value)) {
$parameterValue[$key] = "'$value'";
}
}
$parameterValue = implode(', ', $parameterValue);
}
/** Paste parameter values in SQL */
$sql = substr_replace($sql, $parameterValue, $strPos, strlen($parameterName));
}
}
}
}
You can build an sql string by combining the sql prepared statement with bindings like this way:
$sql = str_replace_array('?', $query->getBindings(), $query->toSql())
str_replace_array(string $search, array $replacement, string $subject): string
PHP's str_replace_array function replaces each instance of $search in $subject with values from $replacement array sequentially.
To print out an SQL query in Doctrine, use:
$query->getResult()->getSql();

Categories