I am trying to make an intermediate class which will log the queries in an array along with their execution time. Everything is fine and it works perfectly. But autocomplete doesnt work when i try to access the intermediate class. How can get the autocomplete to work. I am using Netbeans.
Intermediate classname is Model.
From my application, i have a class by the name Users which extends Model.
class Users extends Model
{
function __construct() {
parent::__construct();
$stmt = $this->prepare('SELECT * FROM users WHERE id=? ');
$stmt->bindValue(1, 1); //$stmt-> auto-complete is unavailable
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($rows); //i get results
}
}
My Model class looks like this.
class Model extends PDO
{
public static $log = array();
private $query_cache = array();
public function __construct() {
parent::__construct(
"mysql:dbname=".MYSQL_DB.";host=".MYSQL_HOST,
MYSQL_USER, MYSQL_PASSWORD
);
}
public function query($query) {
$time = "";
$query = mysql_real_escape_string(preg_replace( '/\s+/', ' ', $query ));
if (key_exists($query,$this->query_cache)
&& is_object($this->query_cache[$query]))
{
$result = $this->query_cache[$query];
} else {
$start = microtime(true);
$result = parent::query($query);
$time = microtime(true) - $start;
$this->query_cache[$query] = $result;
Logger::$logText['DATABASE'][] = array(
'QUERY' => $query,
'TIME' => number_format($time,4)
);
}
return $result;
}
/**
* #return LoggedPDOStatement
*/
public function prepare($query) {
return new LoggedPDOStatement(parent::prepare($query));
}
}
My LoggedPDOStatement looks like this.
class LoggedPDOStatement
{
/**
* The PDOStatement we decorate
*/
private $statement;
public function __construct(PDOStatement $statement) {
$this->statement = $statement;
}
/**
* When execute is called record the time it takes and
* then log the query
* #return PDO result set
*/
public function execute() {
$start = microtime(true);
$result = $this->statement->execute();
$time = microtime(true) - $start;
Model::$log[] = array(
'query' => '[PS] ' . $this->statement->queryString,
'time' => round($time * 1000, 3)
);
return $result;
}
/**
* Other than execute pass all other calls to the PDOStatement object
* #param string $function_name
* #param array $parameters arguments
*/
public function __call($function_name, $parameters) {
return call_user_func_array(
array($this->statement, $function_name), $parameters
);
}
}
Is their any better way of doing this ?
I have fixed this taking suggestions from #cillosis and #Touki
#Touki, i agree i shouldnt extend the PDO Class.
#cillosis, thanks for your comments.
This is the way i have written my class. I have not pasted the full code as its not complete yet. But i have checked it works. And i can log my queries as well. However i am not sure if i will be able to log the execution time.
class Model
{
/**
* The singleton instance
*
*/
static private $PDOInstance;
public function __construct($dsn="", $username = false, $password = false, $driver_options = false) {
if (!self::$PDOInstance) {
try {
self::$PDOInstance = new PDO(
"mysql:dbname=".MYSQL_DB.";host=".MYSQL_HOST,
MYSQL_USER, MYSQL_PASSWORD
);
} catch (PDOException $e) {
die("PDO CONNECTION ERROR: " . $e->getMessage() . "<br/>");
}
}
return self::$PDOInstance;
}
/**
* Initiates a transaction
*
* #return bool
*/
public function beginTransaction() {
return self::$PDOInstance->beginTransaction();
}
public function prepare($statement, $driver_options = false) {
//log the $statement
if (!$driver_options)
$driver_options = array();
return self::$PDOInstance->prepare($statement, $driver_options);
}
}
Related
I am trying to figure out how how to create my own simple cursor based pagination system in PHP and am having difficulty trying to understand how star starting_after and starting_before works as mentioned in this medium.com post for how the company stripe deals with cursor pagination. In my case I am using the id column that is in ascending order to hopefully make this work. The issue that I am having is getting an id for the first "page". Currently my first page would direct to the second page since starting_after leads to the next page and not the currently page. Any advice for how to build this out would be awesome. I already created page based pagination, but think that cursor pagination would be more useful for most of my cases.
I have attached the two files that I have created thus far to try to get this to work.
Pagination class
<?php
require_once "DB.php";
class New_Pagination {
private $table = "";
private $limit;
private $starting_after = "";
private $starting_before = "";
private $db;
public function __construct() {
$this->db = DB::getInstance();
}
public function getLimit(): int {
return $this->limit;
}
public function setLimit(int $limit): void {
$this->limit = $limit;
}
public function getStartingAfter(): string {
return $this->starting_after;
}
public function setStartingAfter(string $starting_after): void {
$this->starting_after = $starting_after;
}
public function getStartingBefore(): string {
return $this->starting_before;
}
public function setStartingBefore(string $starting_before): void {
$this->starting_before = $starting_before;
}
public function getTable(): string {
return $this->table;
}
public function setTable(string $table): void {
$this->table = $table;
}
public function idExists($id) {
$result = $this->db->find(self::getTable(), [
"select" => "id",
"conditions" => "id = $id",
"fetchType" => "single"
]);
if (empty($result)) {
return FALSE;
} else {
return $result->id;
}
}
public function getData($starting_after, $starting_before) {
self::setStartingAfter($starting_after);
self::setStartingBefore($starting_before);
$starting_after = self::getStartingAfter();
$starting_before = self::getStartingBefore();
$data = [];
$order = !empty($starting_after) ? "ASC" : "DESC";
if (empty($starting_after) && empty($starting_before)) {
$data["data"] = $this->db->find(self::getTable(), [
"select" => "*",
"order" => "id ASC",
"limit" => self::getLimit(),
"fetchType" => "all"
]);
} else {
$data["data"] = $this->db->find("carousel_image", [
"select" => "*",
"conditions" => "id >= '$starting_after' OR id <= '$starting_before'",
"order" => "id $order",
"limit" => self::getLimit(),
"fetchType" => "all"
]);
}
$next = self::idExists($data["data"][count($data["data"]) - 1]->id + 1);
$previous = self::idExists($data["data"][0]->id - 1);
$data["cursor"] = [
"next" => $next,
"previous" => $previous
];
return $data;
}
public function generateLink() {
$test = self::getData("", "");
$test2 = [];
$test2[0] = $test;
$i = 0;
do {
$test2[$i] = $test;
$test = self::getData($test["cursor"]["next"], "");
$i++;
$test2[$i] = $test;
} while ($test["cursor"]["next"] !== FALSE);
$test2[$i] = $test;
echo "<ul>";
$j = 1;
foreach ($test2 as $key => $val) {
if ($val["cursor"]["next"] !== FALSE) {
$url = "/process.php?starting_after=" . $val["cursor"]["next"];
echo "<li>";
echo "<a href='$url'>$j</a>";
echo "</li>";
$j++;
}
}
echo "<ul>";
}
}
Test file
$pagination = new New_Pagination();
$pagination->setLimit(2);
$pagination->setTable("carousel_image");
echo "<pre>";
$pagination->generateLink();
echo "</pre>";
The cursors are useful to prevent scan big tables and allow to move in very big sources (files, external resources, etc., etc.). In the majority of the cases, cursors are provided by binary libraries and supported by the core of the related system (mysql, files). If you try to emulate this behavior in not natural way you must take care because you could add overhead and get unexpected results.
In the other hand, is very useful to have a pagination class, but be aware, this class have some problems.
getData is very expensive because it performs 3 queries to get a batch of results
the class is too verbose, the getters and setters add too much noise
the results are sorted using very rare criteria. Remember, if you are in search results you need the same order moving to the next page and moving to the previous page
My suggestions...
Create interfaces
<?php
interface CursorAble {
public function fetchNext ($startingAfter);
public function fetchPrev ($startingBefore);
public function getPreviousLink ();
public function getNextLink ();
}
interface Pageable {
public function getCollectionSize ();
public function getPageSize ();
public function getPagesCount ();
public function getPageLinks ();
}
When you create interfaces you ensures that the classes expose the desired behavior and furthermore delegate the specialized details to the concrete implementations. The concrete implementations can define it dependencies in the constructor, something very good when you relies on dependency injection.
CursorAble implementation example
<?php
class PdoCursorAbleTable implements CursorAble {
private $pdo;
private $table;
private $results;
private $pageSize;
public function __construct (PDO $pdo, $table, $pageSize = 100) {
$this->pdo = $pdo;
$this->table = $table;
$this->pageSize = (int)$pageSize ?: 100;
}
public function fetchNext ($startingAfter) {
$s = $this->pdo->prepare("select * from {$this->table} where id > :starting_after limit {$this->pageSize}");
$s->bindValue(':starting_after', $startingAfter, PDO::PARAM_INT);
$s->execute();
$this->results = $s->fetchAll() ?: [];
return $this->results;
}
public function fetchPrev ($startingBefore) {
$s = $this->pdo->prepare("select * from {$this->table} where id < :starting_before limit {$this->pageSize}");
$s->bindValue(':starting_before', $startingBefore, PDO::PARAM_INT);
$s->execute();
$this->results = $s->fetchAll() ?: [];
return $this->results;
}
public function getPreviousLink () {
return !$this->results ? '' : '?starting_before=' . $this->results[0]->id;
}
public function getNextLink () {
if (!$this->results || count($this->results) < $this->pageSize) return '';
return '?starting_after=' . $this->results[count($this->results)]->id;
}
}
And the Pageable example
<?php
class PdoPageableTable implements Pageable {
private $pdo;
private $table;
private $pageSize;
private $collectionSize;
public function __construct (PDO $pdo, $table, $pageSize = 100) {
$this->pdo = $pdo;
$this->table = $table;
$this->pageSize = $pageSize;
}
public function getCollectionSize () {
if ($this->collectionSize === null) {
$s = $this->pdo->prepare("select count(id) from {$this->table}");
$s->execute();
$this->collectionSize = $s->fetchColumn('0');
}
return $this->collectionSize;
}
public function getPageSize () {
return $this->pageSize;
}
public function getPagesCount () {
return ceil($this->collectionSize / $this->getPageSize());
}
public function getPageLinks () {
$pages = [];
foreach (range(1, $this->getPagesCount()) as $page) {
$pages[] = '?page=' . $page;
}
return $pages;
}
}
The test file
<?php
$pagination = new PdoCursorAbleTable($pdo, 'carousel_image', 2);
echo "<pre>";
$startingAfter = 0;
$results = $pagination->fetchNext($startingAfter);
foreach ($results as $result) {
// do something
}
echo $pagination->getNextLink();
echo "</pre>";
i have created a joomla component and when i click on publish and unpublish button in admin then i am getting such error.
Fatal error: Call to a member function publish() on boolean in ...\libraries\legacy\controller\admin.php on line 209
Please help
UPDATE
my View.html.php
require_once JPATH_COMPONENT . '/helpers/lab.php';
class labViewStructurelist extends JViewLegacy
{
protected $structurelist;
protected $pagination;
public $filterForm;
public $activeFilters;public $state;
public function display($tpl = null)
{
$this->structurelist = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
//print_r($this->pagination->pagesTotal);die();
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
$this->addToolBar();
$this->sidebar = JHtmlSidebar::render();
if (count($errors = $this->get('Errors')))
{
JError::raiseError(500, implode('<br />', $errors));
return false;
}
return parent::display($tpl);
}
protected function addToolBar() {
JToolBarHelper::title( JText::_('COM_LAB_LAB_DDDD'), 'generic.png' );
JToolBarHelper::publish('Structurelist.publish');
JToolBarHelper::unpublish('Structurelist.unpublish');
JToolBarHelper::deleteList('', 'patients.delete', 'JTOOLBAR_DELETE');
JToolBarHelper::preferences('com_lab');
}
}
Controller/stricturelist.php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
// import Joomla controller library
jimport('joomla.application.component.controlleradmin');
class LabControllerStructurelist extends JControllerAdmin
{
public function getModel($name='Structurelist',$prefix='ssModel',$config=array('ignore_request'=>true))
{
$model=parent::getModel($name,$prefix,$config);
return $model;
}
}
models\structurelist.php
defined('_JEXEC') or die;
jimport('joomla.application.component.modellist');
JFormHelper::loadFieldClass('list');
class LabModelStructurelist extends JModelList{
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array(
'id', 'a.id',
'fullname', 'a.fullname',
);
$assoc = JLanguageAssociations::isEnabled();
if ($assoc)
{
$config['filter_fields'][] = 'association';
}
}
parent::__construct($config);
}
public function getListQuery()
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('*');
$query->from('#__ss_structure_tmp');
$search = $this->getState('filter.search');
$limit = $this->getState('filter.limit');
if (!empty($search)) {
$query->where('fullname LIKE "%' . $search .'%" ' );
}
if (!empty($limit)) {
$query->setLimit($limit);
}
return $query;
}
protected function populateState($ordering = 'a.fullname', $direction = 'asc')
{
$app = JFactory::getApplication();
if ($layout = $app->input->get('layout'))
{
$this->context .= '.' . $layout;
}
$search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
$this->setState('filter.search', $search);
parent::populateState();
}
}
You need to provide the model to your controller using the getModel() method. Look in the articles controller of the com_content for example.
You need to add a table file with the publish function within:
defined('_JEXEC') or die;
use Joomla\Utilities\ArrayHelper;
class labTableStructurelist extends JTable
{
/**
* Constructor
*
* #param JDatabase &$db A database connector object
*/
public function __construct(&$db)
{
parent::__construct('#__ss_structure_tmp', 'id', $db);
}
/**
* Overloaded bind function to pre-process the params.
*
* #param array $array Named array
* #param mixed $ignore Optional array or list of parameters to ignore
*
* #return null|string null is operation was satisfactory, otherwise returns an error
*
* #see JTable:bind
* #since 1.5
*/
public function bind($array, $ignore = '')
{
$input = JFactory::getApplication()->input;
$task = $input->getString('task', '');
if (($task == 'save' || $task == 'apply') && (!JFactory::getUser()->authorise('core.edit.state', 'com_lab.structurelist.'.$array['id']) && $array['state'] == 1))
{
$array['state'] = 0;
}
if ($array['id'] == 0)
{
$array['created_by'] = JFactory::getUser()->id;
}
if (isset($array['params']) && is_array($array['params']))
{
$registry = new JRegistry;
$registry->loadArray($array['params']);
$array['params'] = (string) $registry;
}
if (isset($array['metadata']) && is_array($array['metadata']))
{
$registry = new JRegistry;
$registry->loadArray($array['metadata']);
$array['metadata'] = (string) $registry;
}
if (!JFactory::getUser()->authorise('core.admin', 'com_lab.structurelist.' . $array['id']))
{
$actions = JAccess::getActionsFromFile(
JPATH_ADMINISTRATOR . '/components/com_lab/access.xml',
"/access/section[#name='user']/"
);
$default_actions = JAccess::getAssetRules('com_lab.structurelist.' . $array['id'])->getData();
$array_jaccess = array();
foreach ($actions as $action)
{
$array_jaccess[$action->name] = $default_actions[$action->name];
}
$array['rules'] = $this->JAccessRulestoArray($array_jaccess);
}
// Bind the rules for ACL where supported.
if (isset($array['rules']) && is_array($array['rules']))
{
$this->setRules($array['rules']);
}
return parent::bind($array, $ignore);
}
/**
* This function convert an array of JAccessRule objects into an rules array.
*
* #param array $jaccessrules An array of JAccessRule objects.
*
* #return array
*/
private function JAccessRulestoArray($jaccessrules)
{
$rules = array();
foreach ($jaccessrules as $action => $jaccess)
{
$actions = array();
foreach ($jaccess->getData() as $group => $allow)
{
$actions[$group] = ((bool) $allow);
}
$rules[$action] = $actions;
}
return $rules;
}
/**
* Overloaded check function
*
* #return bool
*/
public function check()
{
// If there is an ordering column and this is a new row then get the next ordering value
if (property_exists($this, 'ordering') && $this->id == 0)
{
$this->ordering = self::getNextOrder();
}
return parent::check();
}
/**
* Method to set the publishing state for a row or list of rows in the database
* table. The method respects checked out rows by other users and will attempt
* to checkin rows that it can after adjustments are made.
*
* #param mixed $pks An optional array of primary key values to update. If not
* set the instance property value is used.
* #param integer $state The publishing state. eg. [0 = unpublished, 1 = published]
* #param integer $userId The user id of the user performing the operation.
*
* #return boolean True on success.
*
* #since 1.0.4
*
* #throws Exception
*/
public function publish($pks = null, $state = 1, $userId = 0)
{
// Initialise variables.
$k = $this->_tbl_key;
// Sanitize input.
ArrayHelper::toInteger($pks);
$userId = (int) $userId;
$state = (int) $state;
// If there are no primary keys set check to see if the instance key is set.
if (empty($pks))
{
if ($this->$k)
{
$pks = array($this->$k);
}
// Nothing to set publishing state on, return false.
else
{
throw new Exception(500, JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
}
}
// Build the WHERE clause for the primary keys.
$where = $k . '=' . implode(' OR ' . $k . '=', $pks);
// Determine if there is checkin support for the table.
if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
{
$checkin = '';
}
else
{
$checkin = ' AND (checked_out = 0 OR checked_out = ' . (int) $userId . ')';
}
// Update the publishing state for rows with the given primary keys.
$this->_db->setQuery(
'UPDATE `' . $this->_tbl . '`' .
' SET `state` = ' . (int) $state .
' WHERE (' . $where . ')' .
$checkin
);
$this->_db->execute();
// If checkin is supported and all rows were adjusted, check them in.
if ($checkin && (count($pks) == $this->_db->getAffectedRows()))
{
// Checkin each row.
foreach ($pks as $pk)
{
$this->checkin($pk);
}
}
// If the JTable instance value is in the list of primary keys that were set, set the instance.
if (in_array($this->$k, $pks))
{
$this->state = $state;
}
return true;
}
/**
* Define a namespaced asset name for inclusion in the #__assets table
*
* #return string The asset name
*
* #see JTable::_getAssetName
*/
protected function _getAssetName()
{
$k = $this->_tbl_key;
return 'com_lab.structurelist.' . (int) $this->$k;
}
/**
* Returns the parent asset's id. If you have a tree structure, retrieve the parent's id using the external key field
*
* #param JTable $table Table name
* #param integer $id Id
*
* #see JTable::_getAssetParentId
*
* #return mixed The id on success, false on failure.
*/
protected function _getAssetParentId(JTable $table = null, $id = null)
{
// We will retrieve the parent-asset from the Asset-table
$assetParent = JTable::getInstance('Asset');
// Default: if no asset-parent can be found we take the global asset
$assetParentId = $assetParent->getRootId();
// The item has the component as asset-parent
$assetParent->loadByName('com_lab');
// Return the found asset-parent-id
if ($assetParent->id)
{
$assetParentId = $assetParent->id;
}
return $assetParentId;
}
/**
* Delete a record by id
*
* #param mixed $pk Primary key value to delete. Optional
*
* #return bool
*/
public function delete($pk = null)
{
$this->load($pk);
$result = parent::delete($pk);
return $result;
}
}
Please note, that this is just an example of a table file, I can't guarantee that this would work just by copy&paste. Please check com_content/table for another example.
Also add a getTable() to your List single item model:
public function getTable($type = 'view_name', $prefix = 'labTable', $config = array())
{
return JTable::getInstance($type, $prefix, $config);
}
Please check if file structure is correct:
List View
structurelist**s** -> list view with multiple items
|-> controller/structurelists.php
|-> models/structurelists.php
|-> view/structurelists/
Single View / List View (publish() for example)
structurelist -> single item view (edit view)
|-> models/structurelist.php (this is important for the publish() in the list view)
|-> models/forms/structurelist.xml
|-> tables/structurelist.php (this is important for the publish() in the list view)
|-> view/structurelist/
Feel free to comment if you need more help.
I am trying to write my own MVC Framework using this tutorial. Everything worked fine and i completed the tutorial with no issues.
However later i decided to use PDO (PHP Data objects) instead of mysql() functions for Database Operations. So i modified My sqlQuery.php file to use PDO istead of mysql functions.
sqlQuery.php
<?php
class SQLQuery {
private $_dbHandle;
private $_result;
/**
* Connects to database
*
* #param $address
* #param $account
* #param $pwd
* #param $name
*/
function connect($address, $account, $pwd, $name) {
try{
$this->_dbHandle = new PDO("mysql:host=$address;dbname=$name", $account, $pwd);
}catch (PDOException $e) {
die($e->getCode() . " : " . $e->getMessage());
}
}
/** Disconnects from database **/
function disconnect() {
$this->_dbHandle = null;
}
function get($whereClause = array()) {
$query = "select * from $this->_table";
if(is_array($whereClause) && count($whereClause)){
$query .= " where ";
foreach($whereClause as $column=>$value)
$query .= " $column = $value";
}else if(is_int($whereClause)){
$query .= " where id = $whereClause ";
}
return $this->query($query);
}
/**
* Custom SQL Query
*
* #param $query
* #return array|bool
*/
function query($query) {
$this->_result = $this->_dbHandle->query($query);
$this->_result->setFetchMode(PDO::FETCH_CLASS, $this->_model);
if (preg_match("/select/i",$query)) {
$result = array();
$numOfFields = $this->_result->rowCount();
if($numOfFields > 1){
while($result[] = $this->_result->fetch()){
}
}else{
$result = $this->_result->fetch();
}
return $result;
}
return true;
}
}
Now when In my Controller when i print $this->Item->get() I get all the results in my database as Objects of Items Model and $this->Item->get(2) gives me the item object with id=2 as expected.
However, I don't like the idea that my API needs to be to call an additional method get() to get the objects of the Items Model, Instead it makes more sense that when a Items Model is initialized i get the desired object and so my API can be $this->item->mycolumnName.
To achieve this i tried to move the get() call in the Models constructor like this :
model.php
<?php
class Model extends SQLQuery{
protected $_model;
function __construct() {
$this->connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
$this->_model = get_class($this);
$this->_table = strtolower($this->_model)."s";
$this->get();
}
function __destruct() {
}
}
However this gives me a Fatal error
Fatal error: Maximum function nesting level of '256' reached, aborting! in /var/www/html/FitternityAssignment/library/sqlquery.php on line 59
I don't know what i have done wrong.
The issue is line 59: while($result[] = $this->_result->fetch()){
function query($query) {
$this->_result = $this->_dbHandle->query($query);
$this->_result->setFetchMode(PDO::FETCH_CLASS, $this->_model);
if (preg_match("/select/i",$query)) {
$result = array();
$numOfFields = $this->_result->rowCount();
if($numOfFields > 1){
while($result[] = $this->_result->fetch()){
}
}else{
$result = $this->_result->fetch();
}
return $result;
}
return true;
}
Let's narrow in on the issue:
while($result[] = $this->_result->fetch()){
}
Infinite loop.
When fetch() returns something, then it is added to $result, because $result is an array, and then of course it evaluates to true because the size of $result grows each time a fetched result is added.
$results = [];
while($row = $this->_result->fetch()){
$results[] = $row; // or whatever you need to do with the row
}
Might work. I say might because it depends on what $this->_result->fetch() returns when no results are left. I'll assume false or null, in which case the above will work, because when there are no more results then fetch() will return null or false and $row will then evaluate to false.
I want to view the SQL statement that is about to be executed below :
<?php
//Deleting existing robot
$success = $connection->delete(
"robots",
"id = 101"
);
//Next SQL sentence is generated
DELETE FROM `robots` WHERE `id` = 101
How can I add some kind of listener or just plain var_dump the select query that is about to generated by the $connection->delete
Thanks
The way I settled on is to use a logger class and the event system: Phalcon Events Manager
You create a class that extends the logger adapter
<?php
namespace PhalconX\Logger\Adapter;
/**
* Basic Array based Logging for debugging Phalcon Operations
* #package PhalconX\Logger\Adapter
*/
class Basic extends \Phalcon\Logger\Adapter
{
private $data = array();
/**
* Add a statement to the log
* #param string $statement
* #param null $type
* #param array $params
* #return $this|\Phalcon\Logger\Adapter
*/
public function log($statement, $type=null, array $params=null)
{
$this->data[] = array('sql'=>$statement, 'type'=>$type, 'params'=>$params); // array('sql'=>$statement, 'type'=>$type);
return $this;
}
/**
* return the log
* #return array
*/
public function getLog(){
return $this->data;
}
/**
* Required function for the interface, unused
* #param $message
* #param $type
* #param $time
* #param $context
*/
public function logInternal($message, $type, $time, $context){
}
/**
* Required function for the interface, unused
*/
public function getFormatter(){
}
/**
* Required function for the interface, unused
*/
public function close(){
}
}
and then attach it to your database, and plumb in the events by type
$eventsManager = new \Phalcon\Events\Manager();
$logger = new \PhalconX\Logger\Adapter\Basic();
$profiler = $phalconDi->getProfiler();
//Listen all the database events
/** #var $event \Phalcon\Events\Event */
/** #var $phalconConnection \Phalcon\Db\Adapter\Pdo\Mysql */
$eventsManager->attach('db', function($event, $phalconConnection) use ($logger, $profiler) {
if ($event->getType() == 'beforeQuery') {
$profiler->startProfile($phalconConnection->getSQLStatement());
$logger->log($phalconConnection->getSQLStatement(), \Phalcon\Logger::INFO, $phalconConnection->getSQLVariables());
}
if ($event->getType() == 'afterQuery') {
$profiler->stopProfile();
}
});
This presumes you have a 'db' key in your dependency injector.
My logger just stores the queries in an array so I can output them at the bottom of my page.
My trick to factor a closest to real SQL statement out of those prepared ones:
function statement2sql($connection) {
$stmt = $connection->getSQLStatement();
foreach ( $connection->getSQLVariables() as $k => $v ) {
// replaces :p1, .. :p11 .. and defined binds with binded values
$stmt = preg_replace('/:' . $k . '([^A-Za-z0-9])/', '\'' . $v . '\'$1', $stmt);
}
return $stmt;
}
Defined as method or function, you can push its result to profiler as in accepted answer:
$eventsManager->attach('db:beforeQuery', function($event, $connection) {
$profiler->startProfile(statement2sql($connection));
}
$eventsManager->attach('db:afterQuery', function($event, $connection) {
$profiler->stopProfile();
}
or store in other way - using logger or other debugging class.
I've had good luck wrapping my SQL execute call in a try/catch, then printing the exception. Any error message returned by MySQL is in the exception's message, which will contain the raw query.
function get_item_by_id ($db_connection, $item_id) {
try {
$stmt = 'SELECT * FROM inventory WHERE id=:id';
$prepared_stmt = $db_connection->prepare ($stmt);
$result = $db_connection->executePrepared ($prepared_stmt,
array (
"id" => $item_id
),
array (
"id" => Column::BIND_PARAM_INT
)
);
$result->setFetchMode (Phalcon\Db::FETCH_OBJ);
$item_arr = $result->fetchAll ();
return $item_arr;
}
catch (Exception $e) {
print_r ($e->getMessage());
}
}
Another option, my personal preference, is to look at the situation from the perspective of the database. Most SQL databases allow you to set a trigger for certain events (in your case, DELETE), and generate a log entry with the full text of the incoming request.
Reference: https://stackoverflow.com/a/10671410/1504367.
This question already has answers here:
Call to undefined method mysqli_stmt::get_result
(10 answers)
Closed 5 years ago.
On my server i get this error
Fatal error: Call to undefined method mysqli_stmt::get_result() in /var/www/virtual/fcb/htdocs/library/mysqlidbclass.php on line 144
And I am having a wrapper like this:
<?php
/* https://github.com/aaron-lord/mysqli */
class mysqlidb {
/**
* Set up the database connection
*/
public function __construct($server,$user,$password,$db){
$this->connection = $this->connect($server, $user, $password, $db, true);
}
/**
* Connect to the database, with or without a persistant connection
* #param String $host Mysql server hostname
* #param String $user Mysql username
* #param String $pass Mysql password
* #param String $db Database to use
* #param boolean $persistant Create a persistant connection
* #return Object Mysqli
*/
private function connect($host, $user, $pass, $db, $persistant = true){
$host = $persistant === true ? 'p:'.$host : $host;
$mysqli = new mysqli($host, $user, $pass, $db);
if($mysqli->connect_error)
throw new Exception('Connection Error: '.$mysqli->connect_error);
$mysqli->set_charset('utf8');
return $mysqli;
}
/**
* Execute an SQL statement for execution.
* #param String $sql An SQL query
* #return Object $this
*/
public function query($sql){
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
$stmt = $this->connection->query($sql);
# Affected rows has to go here for query :o
$this->affected_rows = $this->connection->affected_rows;
$this->stmt = $stmt;
return $this;
}
else {
throw new Exception;
}
}
/**
* Prepare an SQL statement
* #param String $sql An SQL query
* #return Object $this
*/
public function prepare($sql){
unset($this->stmt);
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
# Ready the stmt
$this->stmt = $this->connection->prepare($sql);
if (false===$this->stmt)
{
print('prepare failed: ' . htmlspecialchars($this->connection->error)."<br />");
}
return $this;
}
else {
throw new Exception();
}
}
public function multi_query(){ }
/**
* Escapes the arguments passed in and executes a prepared Query.
* #param Mixed $var The value to be bound to the first SQL ?
* #param Mixed $... Each subsequent value to be bound to ?
* #return Object $this
*/
public function execute(){
if(is_object($this->connection) && is_object($this->stmt)){
# Ready the params
if(count($args = func_get_args()) > 0){
$types = array();
$params = array();
foreach($args as $arg){
$types[] = is_int($arg) ? 'i' : (is_float($arg) ? 'd' : 's');
$params[] = $arg;
}
# Stick the types at the start of the params
array_unshift($params, implode($types));
# Call bind_param (avoiding the pass_by_reference crap)
call_user_func_array(
array($this->stmt, 'bind_param'),
$this->_pass_by_reference($params)
);
}
if($this->stmt->execute()){
# Affected rows to be run after execute for prepares
$this->affected_rows = $this->stmt->affected_rows;
return $this;
}
else {
throw new Exception($this->connection->error);
}
}
else {
throw new Exception;
}
}
/**
* Fetch all results as an array, the type of array depend on the $method passed through.
* #param string $method Optional perameter to indicate what type of array to return.'assoc' is the default and returns an accociative array, 'row' returns a numeric array and 'array' returns an array of both.
* #param boolean $close_stmt Optional perameter to indicate if the statement should be destroyed after execution.
* #return Array Array of database results
*/
public function results($method = 'assoc', $close_stmt = false){
if(is_object($this->stmt)){
$stmt_type = get_class($this->stmt);
# Grab the result prepare() & query()
switch($stmt_type){
case 'mysqli_stmt':
$result = $this->stmt->get_result();
$close_result = 'close';
break;
case 'mysqli_result':
$result = $this->stmt;
$close_result = 'free';
break;
default:
throw new Exception;
}
$this->num_rows = $result->num_rows;
# Set the results type
switch($method) {
case 'assoc':
$method = 'fetch_assoc';
break;
case 'row':
//return 'fetch_row';
$method = 'fetch_row';
break;
default:
$method = 'fetch_array';
break;
}
$results = array();
while($row = $result->$method()){
$results[] = $row;
}
$result->$close_result();
return $results;
}
else {
throw new Exception;
}
}
/**
* Turns off auto-committing database modifications, starting a new transaction.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function start_transaction(){
if(is_object($this->connection)){
return $this->connection->autocommit(false);
}
}
/**
* Commits the current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function commit(){
if(is_object($this->connection)){
# Commit!
if($this->connection->commit()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Rolls back current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function rollback(){
if(is_object($this->connection)){
# Commit!
if($this->connection->rollback()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Return the number of rows in statements result set.
* #return integer The number of rows
*/
public function num_rows(){
return $this->num_rows;
}
/**
* Gets the number of affected rows in a previous MySQL operation.
* #return integer The affected rows
*/
public function affected_rows(){
return $this->affected_rows;
}
/**
* Returns the auto generated id used in the last query.
* #return integer The last auto generated id
*/
public function insert_id(){
if(is_object($this->connection)){
return $this->connection->insert_id;
}
}
/**
* Fixes the call_user_func_array & bind_param pass by reference crap.
* #param array $arr The array to be referenced
* #return array A referenced array
*/
private function _pass_by_reference(&$arr){
$refs = array();
foreach($arr as $key => $value){
$refs[$key] = &$arr[$key];
}
return $refs;
}
}
?>
Is there any way to use another function so that I won't have to rewrite whole app? Please tell me if any.
Please read the user notes for this method:
http://php.net/manual/en/mysqli-stmt.get-result.php
It requires the mysqlnd driver. if it isn't installed on your webspace you will have to work with BIND_RESULT & FETCH
http://www.php.net/manual/en/mysqli-stmt.bind-result.php
http://www.php.net/manual/en/mysqli-stmt.fetch.php
Extracted from here
The reason for this error is that your server doesn't have the mysqlnd driver driver installed. (See here.) If you have admin privileges you could install it yourself, but there is also an easier way:
I have written two simple functions that give the same functionality as $stmt->get_result();, but they don't require the mysqlnd driver.
You simply replace
$result = $stmt->get_result(); with $fields = bindAll($stmt);
and
$row= $stmt->get_result(); with $row = fetchRowAssoc($stmt, $fields);.
(To get the numbers of returned rows you can use $stmt->num_rows.)
You just have to place these two functions I have written somewhere in your PHP Script. (for example right at the bottom)
function bindAll($stmt) {
$meta = $stmt->result_metadata();
$fields = array();
$fieldRefs = array();
while ($field = $meta->fetch_field())
{
$fields[$field->name] = "";
$fieldRefs[] = &$fields[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $fieldRefs);
$stmt->store_result();
//var_dump($fields);
return $fields;
}
function fetchRowAssoc($stmt, &$fields) {
if ($stmt->fetch()) {
return $fields;
}
return false;
}
How it works:
My code uses the $stmt->result_metadata(); function to figure out how many and which fields are returned and then automatically binds the fetched results to pre-created references. Works like a charm!
Also posted here.