Brackets in Doctrine query - php

hi
i have to nest some or / and conditions
but i need brackets in my sql statement to do it in the right order
but how do you make this
is should be in this form
(... OR ...) AND ...
thnx

According to this blog post, "Solving the Doctrine Parenthesis Problem", you need to do a $query->where("(ConditionA OR ConditionB) AND ConditionC");
That may look like:
Doctrine_Query::create()
->from(...)
->where('A = ? OR B = ?', array(valA, valB))
->andWhere('C = ?', valC);
The poster does, however, provide a more generic solution, whereParenWrap(), by extending Doctrine_Query:
DQ::create()
->from(...)
->where('A = ?', valA)
->orWhere('B = ?', valB)
->whereParenWrap()
->andWhere('C = ?', valC);

Addon for the answer: Brackets in Doctrine query
/*
* This is a simple short-hand wrapper for Doctrine_Query. It provides
* a shorter class name and a few additional functions.
*/
class DQ extends Doctrine_Query
{
/**
* Returns a DQ object to get started
*
* #return DQ
*/
public static function create($conn = null, $class = null) {
return new DQ($conn, $class);
}
/**
* This function will wrap the current dql where statement
* in parenthesis. This allows more complex dql statements
* It can be called multiple times during the creation of the dql
* where clause.
*
* #return $this
*/
public function whereParenWrap() {
$where = $this->_dqlParts['where'];
if (count($where) > 0) {
array_unshift($where, '(');
array_push($where, ')');
$this->_dqlParts['where'] = $where;
}
return $this;
}
}
?>
Slightly modified.
Taken from: https://gist.github.com/888386/634c51993aaf16565690be10da7b8f13a227020a

Now you can use Expr::orX()
/** To combine all the OR conditions in one parenthesis, we will collect the conditions in one array */
$orX = [];
foreach ($dto->userNameSearchPhrases as $key => $userNameSearchPhrase) {
$orX[] = $qb->expr()->like('us.username', ':userNameSearchPhrase' . $key);
$qb->setParameter('userNameSearchPhrase' . $key, $userNameSearchPhrase);
}
/** To pass parameters to the Expr::orX() method from an array, use ReflectionMethod */
$reflectionMethod = new \ReflectionMethod(\Doctrine\ORM\Query\Expr::class, 'orX');
$orXObject = $reflectionMethod->invokeArgs($qb->expr(), $orX);
$qb->andWhere($orXObject);

Related

CodeIgniter - Best Model Approach

This has been bothering me for quite some time now.
Controller, on my take, is where I do validation, model calls, and displaying of data. And on the Model, which has only one purpose, is where I make my SQL queries.
But what is the best approach on Model. Do I have to make a lot of functions, with different conditions/approaches, or do I have to do it all in a single function, dedicated to a single table in my database. Take the code below for example:
Multiple Functions:
class Sword_model extends CI_Model {
public function __construct()
{
$this->load->database();
$this->load->library('session');
$this->load->helper('url_helper');
}
public function getsword($swordid = NULL)
{
if($swordid === NULL){
$query = $this->db->query("SELECT * FROM sword_tb");
return $query->result_array();
}
$query = $this->db->query("SELECT * FROM sword_tb WHERE sword_id = ?", array($swordid));
return $query->row_array();
}
public function getstrongswords($strong = NULL)
{
if($strong === NULL){
return false;
}
$query = $this->db->query("SELECT * FROM sword_tb WHERE strong = ?", array($strong));
return $query->result_array();
}
/*** AND MORE SUCCEEDING FUNCTIONS BELOW FOR DIFFERENT COLUMNS/CONDITIONS ***/
}
Pros: This is straight through and easier to understand (and I think is faster)
Cons: You have to manually create functions for different conditions/columns
Single Function:
class Sword_model extends CI_Model {
public function __construct()
{
$this->load->database();
$this->load->library('session');
$this->load->helper('url_helper');
}
public function getsword($column = NULL, $value = NULL, $condition = 'AND')
{
$query = 'SELECT * FROM sword_tb';
$count = count($column);
for($x = 0; $x < $count; $x++){
if($x > 0){
$query .= ' '.$condition;
} else {
$query .= ' WHERE (';
}
$query .= ' '.$column[$x].' = ?';
}
$query .= ' ORDER BY sword_name';
$query = $this->db->query($query, $value);
return $query->result_array();
}
}
with this single function approach, you can call this function by putting arrays as parameters like this:
$this->Sword_model->getsword(array('sword', 'strong'), array(3, 1), 'OR');
And the query will look like this:
SELECT * FROM sword WHERE (sword = ? OR strong = ?) ORDER BY sword_name
And if you left it behind blank, the query will look like this:
SELECT * FROM sword ORDER BY sword_name
Pros: Flexible
Cons: Slower (?) than the first approach
What is more ideal between the two? Or is there any other more ideal way?
I prefer to have each table to have it's own model with different preferable methods as you do in "Multiple Model". This way it is more maintainable and easier to approach & understand the codes. The second one is though it's harder to approach and it may not be useful on all scenario.
I have been using Codeigniter MVC & HMVC for more than 2 years. First one is my choice for so far and it helps me to check and maintain my codes and also helps in my future updates/codes.
You can try ORM ( Object-relational mapping )
Datamapper is an ORM library .It is designed to map your Database tables into easy to work with objects .
After successful installation of Datamapper
If you have a table called users
Create a model with name user
<?php
class User extends DataMapper {
function __construct($id = NULL)
{
parent::__construct($id);
}
}
In your controller, you can simply access data by
$u = new user(); // Singular of model name is required
$u->get();
foreach ($u as $user => $value)
{
echo $user->name;
}
By this method you have to create each models for your tables and access data through your controller
I prefer one generic model using which I can execute all mysql queries (which are not so complex, otherwise you need to write a query string and get it executed in few cases if there is a need of some too much complex query.) Otherwise you can call the models' generic functions from your controller by passing data array and table name and rest will be managed by the model.
Here is the generic model I use:
<?php
/*
All user module related databse functions
Author : Himanshu Upadhyay (himanshuvarun#gmail.com)
*/
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class User_Model extends MY_Model {
public function __construct() {
parent::__construct();
}
public function get_rows($filters = array(), $table = TBL_USER) {
return parent::get_rows($filters, $table);
}
public function get_columns($table = TBL_USER) {
return parent::get_columns($table);
}
public function update_table($data, $where, $table = TBL_USER, $set = '') {
return parent::update_table($data, $where, $table, $set = '');
}
public function get_count($filters = array(), $table = TBL_USER) {
return parent::get_count($filters, $table);
}
public function insert($data, $table = TBL_USER) {
return parent::insert($data, $table);
}
public function delete($where, $table = TBL_USER) {
return parent::delete($where, $table);
}
/* End of file user_model.php */
/* Location: ./application/models/user_model.php */
?>
And in your controller, you can call the model function like :
$user_data['first_name'] = 'ABC';
$user_data['last_name'] = 'XYZ';
$this->user_model->insert($user_data, 'tbl_users'); // This is calling `insert` function of user model with first array argument with the data with column names as keys of the array and 2nd argument is the table name.
Conclusion: So by this approach, you can load user_model in all the controllers and you can use all its generic functions in all of the controllers. So this approach avoids redundant model functions to fetch, insert and update the data as well as it saves us by defining different models for each tables.
Most of the time multiple functions are easier to debug, comprehend and maintain.
That said, you could make your multi-method model code a lot less repetitive.
Consider the following. (__construct() not shown cause yours is fine.)
class Sword_model extends CI_Model
{
protected $get_all_sql = 'SELECT * FROM sword_tb';
protected $get_where_sql = "SELECT * FROM sword_tb WHERE sword_id = ?";
public function getsword($swordid = NULL)
{
$sql = isset($swordid) ? $this->get_where_sql : $this->get_all_sql;
$bind = isset($swordid) ? $swordid : FALSE;
return $this->do_Query($sql, $bind);
}
public function getstrongswords($strong = NULL)
{
if(isset($strong))
{
return $this->do_Query($this->get_where_sql, $strong);
}
//Returning an empty array means the controller can skip doing a conditional
//check for a model return === FALSE.
//foreach() won't choke on an empty array, but it will choke if passed FALSE
return array();
}
protected function do_Query($sql, $binds = FALSE)
{
$query = $this->db->query($sql, $binds);
return $query->result_array();
}
}
However, the "flexible" approach can be useful is certain circumstances.
The speed difference between "singles" vs "flexible" is negligible and not a consideration. What does need to be considered is that "flexible" quickly becomes unwieldy as you try to respond to more "conditions".
There is something that will make writing "flexible" model methods easier - easier to write, comprehend, debug and maintain. Instead of manually constructing query strings and passing them to $this->db->query() use Query Builder. It is designed exactly for situations where you need to conditionally build a query statement.
Your "flexible" version of getsword() has at least one limitation and that is that you cannot SELECT columns that are not part of the WHERE clause.
Using Query Builder here is one way you could implement a flexible method that builds queries for both AND WHERE and OR WHERE clauses. Hopefully the DocBlock before the method will provide some insight.
class Sword_model extends CI_Model
{
protected $get_all_sql = 'SELECT * FROM sword_tb';
protected $get_where_sql = "SELECT * FROM sword_tb WHERE sword_id = ?";
/**
*
* #param mixed $columns The columns to retrieve. Can be either a string
* e.g. 'title, content, date',
* or it can be an array e.g. array('title', 'content', 'date')
*
* #param array $where If provided, must be an associative array where
* the key => value is 'field_name' => value_to_match, e.g.
* array('title' => "Kill Bill")
* $where requires a different structure when the $condition argument is
* "OR". In this case the value part should provide multiple values.
* These values can be provided either in an array
* or a comma separated string list. For instance:
* As an array, $where = array('field_name' => array('val1', 'val2'));
* As a string, $where = array('field_name' => 'val1, val2'));
*
* #param string $condition For this example can be either 'AND' (default) or 'OR'
*/
public function getsword($columns = NULL, $where = NULL, $condition = 'AND')
{
if(!empty($columns)) //No $columns means SELECT *
{
$this->db->select($columns);
}
$condition = strtoupper($condition); //Don't assume
if(!empty($where))
{
if($condition === 'OR')
{
foreach($where as $key => $values)
{
if(is_string($values))
{
$values = explode(', ', $values);
}
if(is_array($values))
{
foreach($values as $matching)
{
$match = [$key => $matching];
$this->db->or_where($match);
}
}
else
{
$this->db->or_where($key, $values);
}
}
}
else
{
$this->db->where($where);
}
}
return $this->db
->order_by('sword_name')
->get("sword_tb")
->result_array();
}
}
You might look at the block
foreach($values as $matching)
{
$match = [$key => $matching];
$this->db->or_where($match);
}
and question the use of or_where() before calling where(). Query Builder is smart enough to know if any other WHEREs have been added and won't put "OR" in front of "WHERE" if it's not needed.
Let's look at some usage examples.
$this->sword_model->getsword();
Produces the query statement
SELECT * FROM sword_tb ORDER BY sword_name
$this->sword_model->getsword('model, sword_name, cost', array('model' => 'Broad'));
produces
SELECT model, sword_name, cost FROM sword_tb WHERE model = Broad ORDER BY sword_name
$this->sword_model->getsword(NULL, array('model' => 'Broad', 'cost <' => 100));
SELECT * FROM sword_tb WHERE model = Broad AND cost < 100 ORDER BY sword_name
$this->sword_model->getsword('model, sword_name, cost',
array('model' => 'Broad, Samurai', 'sword_name' => 'Excalibur'), 'or');
SELECT model, sword_name, cost FROM sword_tb WHERE model = Broad OR model = Samurai OR sword_name = Excalibur ORDER BY sword_name
$this->sword_model->getsword(NULL,
array('model' => 'Broad, Samurai', 'sword_name' => ['Excalibur', 'Fred']), 'or');
SELECT * FROM sword_tb WHERE model = Broad OR model = Samurai OR sword_name = Excalibur OR sword_name = Fred ORDER BY sword_name
NOTE: I removed the back-tics from the generated queries string because here on SO it looked weird. Rest assured, everything is properly escaped.
If you're worried about speed, I benchmarked the above. The longest time required to build a query statement was 0.0007 second. To build all five queries required a total of 0.0022 seconds - an average of 0.00044 each.
Note, this is time to build the statement. The time to retrieve data is not included because, well... I don't have the data.

How do I view a SQL Statement in Phalconphp?

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.

Laravel Repository caching and pagination

I have created a base repository and am now extending it to add caching, but the problem I seem to be experiencing more than most is pagination
in my all() method, I do the following without caching:
public function all($pagination = 20)
{
try
{
$query = $this->model->newQuery();
return $this->handlePagination($query, $pagination);
}
catch (Exception $e)
{
throw $e;
}
}
protected function handlePagination($query, $pagination)
{
if (is_null($pagination))
{
return $query;
}
$collection = $query->paginate($pagination);
return $collection;
}
This is working well, but when I try to implement caching, I want to cache each model individually and store the keys for each collection, so, if I was paginating all of the entries, I would store each paginated collection in a cache:
cache set.1 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
cache set.2 [21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]
etc...
the problem is that is seems impossible to use the actual pagination class to return the results since you are paginating only the id
I could return the data and the paginator separately but that seems very hacky.
Is there a way to repopulate the Paginator class with model data without over-writing the whole thing?
EDIT
I was thinking about something like this:
public function all($pagination = 20)
{
try
{
$cache_key = $this->cache_key . '.all';
$ids = Cache::get($cache_key);
if (! $ids)
{
$query = $this->model->newQuery();
$ids = $query->pluck('id');
Cache::put($cache_key, $ids, $this->cache_ttl);
}
$ids = $this->handlePagination($ids, $pagination);
$collection = new Collection();
foreach ($ids as $id)
{
if ($model = $this->find($id))
{
$collection->put($id, $model);
}
}
return $collection;
}
catch (Exception $e)
{
throw $e;
}
}
/**
* #var \Illuminate\Pagination\Paginator
*/
protected $paginator;
public function handlePagination($array, $pagination = 20)
{
if (!is_null($pagination))
{
$this->paginator = Paginator::make($array, count($array), $pagination);
return $this->paginator->getItems();
}
return $array;
}
public function getPaginator()
{
return $this->paginator;
}
I had to implement caching in a project where I work and I faced a similar issue but not with pagination. But the approach should be the same.
Laravel handles internally by default query caching if the model is told to do so.
What I did was creating a class that all the objects I want to cache should extend somehow. Then you can use pagination without even thinking about caching.
In the code below, pay special attention to the following method overrides:
newQuery
newBaseQueryBuilder
newFromBuilder
The CacheModel class looks like this:
<?php
class CacheModel extends Eloquent
{
/**
* Holds the query builder from which this model was fetched from.
*
* #var Illuminate\Database\Query\Builder
*/
protected $queryBuilder;
/**
* Overrides Illuminate\Database\Eloquent\Model's newQuery().
* We need to do this to store the query builder in our class for caching purpuses.
*
* #return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery($excludeDeleted = true)
{
$eloquentBuilder = parent::newQuery($excludeDeleted);
return $eloquentBuilder->rememberForever();
}
/**
* Overrides Illuminate\Database\Eloquent\Model's newBaseQueryBuilder().
* We need to do this to store the query builder in our class for caching purpuses.
*
* #return \Illuminate\Database\Query\Builder
*/
protected function newBaseQueryBuilder()
{
$queryBuilder = parent::newBaseQueryBuilder();
$this->queryBuilder = $queryBuilder;
return $queryBuilder;
}
/**
* Overrides Illuminate\Database\Eloquent\Model's newFromBuilder().
* We need to do this to update the cache.
*
* #return an instance of the specified resource
*/
public function newFromBuilder($attributes = array())
{
$object = parent::newFromBuilder($attributes);
$that = $this;
$referencedCacheKeysFromObject = Cache::rememberForever($object->getCacheIdKey(), function() use ($that){
return array( $that->getQueryBuilder()->getCacheKey() => true );
});
if ( !isset($referencedCacheKeysFromObject[$this->getQueryBuilder()->getCacheKey()] ))
{
# Update the cache entries that hold the object
$referencedCacheKeysFromObject[$this->getQueryBuilder()->getCacheKey()] = true;
Cache::forget($object->getCacheIdKey());
Cache::forever($object->getCacheIdKey(), $referencedCacheKeysFromObject);
}
$referencedCacheKeysFromObjectTable = Cache::rememberForever($object->getCacheTableKey(), function() use ($that){
return array( $that->getQueryBuilder()->getCacheKey() => true );
});
if ( !isset( $referencedCacheKeysFromObjectTable[$this->getQueryBuilder()->getCacheKey()] ))
{
# Udate the cache entries that hold objects from the object's table.
$referencedCacheKeysFromObjectTable[$this->getQueryBuilder()->getCacheKey()] = true;
Cache::forget($object->getCacheTableKey());
Cache::forever($object->getCacheTableKey(), $referencedCacheKeysFromObjectTable);
}
return $object;
}
/**
* Overrides Illuminate\Database\Eloquent\Model's save().
* We need to do this to clean up the cache entries related to this object.
*
* #return \Illuminate\Database\Query\Builder
*/
public function save(array $attributes = array())
{
if (!$this->exists)
{
# If the object doesn't exists, it means that the object is gonna be created. So refresh all queries involving the object table.
# This is needed because the new created object might fell within one of the cache entries holding references to objects of this type.
$this->cleanUpCacheQueriesOfObjectTable();
}
$this->cleanUpCacheQueriesOfObject();
return parent::save();
}
/**
* Overrides Illuminate\Database\Eloquent\Model's delete().
* We need to do this to clean up the cache entries related to this object.
*
*/
public function delete()
{
$this->cleanUpCacheQueriesOfObject();
return parent::delete();
}
/**
* Overrides Illuminate\Database\Eloquent\Model's delete().
* We need to do this to clean up the cache entries related to this object.
*
*/
public static function destroy($id)
{
$this->find($id)->cleanUpCacheQueriesOfObject();
Cache::forget($this->getCacheIdKey($id));
return parent::destroy($id);
}
/**
* Returns the asociated query builder from which the model was created
*
* #return \Illuminate\Database\Query\Builder
*/
public function getQueryBuilder()
{
return $this->queryBuilder;
}
/**
* Cleans up all the cache queries that involve this object, if any.
*
*/
private function cleanUpCacheQueriesOfObject()
{
# Clean up the cache entries referencing the object as we need to re-fetch them.
if ( $referencedCacheKeys = Cache::get($this->getCacheIdKey()) )
{
foreach ($referencedCacheKeys as $cacheKey => $dummy)
{
Cache::forget($cacheKey);
}
}
}
/**
* Cleans up all the cache queries that involve this object table, if any.
* Needed when a a new object of this type is created.
* The cache needs to be refreshed just in case the object fells into
* one of the cache queries holding entries from the same type of the object.
*
*/
private function cleanUpCacheQueriesOfObjectTable()
{
# Clean up the cache entries referencing the object TABLE as we need to re-fetch them.
if ( $referencedCacheKeys = Cache::get($this->getCacheTableKey()) )
{
foreach ($referencedCacheKeys as $cacheKey => $dummy)
{
Cache::forget($cacheKey);
}
}
}
/**
* Returns a string containing a key for the table cache
*
*/
private function getCacheTableKey()
{
return '_' . $this->getTable();
}
/**
* Returns a string containing a key for the object cache
*
*/
private function getCacheIdKey($id = null)
{
if (!isset($id))
{
if (isset($this->id))
$id = $this->id;
else
$id = md5(serialize($this->getAttributes()));
}
return $this->getCacheTableKey() . '_' . $id;
}
}
Then you can do another class extending this CacheModel that could be called PaginateModel and you can do whatever pagination operations you'd like to do.
Of course the rest of the objects should extend this PaginateModel class.
EDIT: Added some if conditions in the method newFromBuilder as I just figuered out that there's a bug in APC since 3.1.3.
Pass the page number along as well for caching. Sonething like this
$currentPg = Input::get('page') ? Input::get('page') : '1';
$boards = Cache::remember('boards'.$currentPg, 60, function(){ return WhatEverModel::paginate(15); });
You need create the paginator manually as instructed in Laravel documentation. So basically you need to return paginator objects from your repository and those can be cached easily. Other option is to generate the paginator on the fly based on the cached/queried parameters.

How to paginate a native query in Doctrine 2?

Doctrine 2 has the Doctrine\ORM\Tools\Pagination\Paginator class which can be used to paginate normal DQL queries.
However if I pass it a native query, I get this error:
Catchable fatal error: Argument 1 passed to Doctrine\ORM\Tools\Pagination\Paginator::cloneQuery() must be an instance of Doctrine\ORM\Query, instance of Doctrine\ORM\NativeQuery given
I've tried removing the type-hinting from the paginator class in the cloneQuery method, but this just gives further errors because other bits of the paginator class expect methods found in Query that aren't in NativeQuery.
Is there any easy way of paginating the native queries without needing to build a new paginator class or fetching every row from the database into an array?
I made my own paginator adapter class compatible with Zend_Paginator.
Probably won't be the most flexible since it relies on there being a " FROM " near the start of the query (see the count() method) but it's a relatively quick and easy fix.
/**
* Paginate native doctrine 2 queries
*/
class NativePaginator implements Zend_Paginator_Adapter_Interface
{
/**
* #var Doctrine\ORM\NativeQuery
*/
protected $query;
protected $count;
/**
* #param Doctrine\ORM\NativeQuery $query
*/
public function __construct($query)
{
$this->query = $query;
}
/**
* Returns the total number of rows in the result set.
*
* #return integer
*/
public function count()
{
if(!$this->count)
{
//change to a count query by changing the bit before the FROM
$sql = explode(' FROM ', $this->query->getSql());
$sql[0] = 'SELECT COUNT(*)';
$sql = implode(' FROM ', $sql);
$db = $this->query->getEntityManager()->getConnection();
$this->count = (int) $db->fetchColumn($sql, $this->query->getParameters());
}
return $this->count;
}
/**
* Returns an collection of items for a page.
*
* #param integer $offset Page offset
* #param integer $itemCountPerPage Number of items per page
* #return array
*/
public function getItems($offset, $itemCountPerPage)
{
$cloneQuery = clone $this->query;
$cloneQuery->setParameters($this->query->getParameters(), $this->query->getParameterTypes());
foreach($this->query->getHints() as $name => $value)
{
$cloneQuery->setHint($name, $value);
}
//add on limit and offset
$sql = $cloneQuery->getSQL();
$sql .= " LIMIT $itemCountPerPage OFFSET $offset";
$cloneQuery->setSQL($sql);
return $cloneQuery->getResult();
}
}
public function countTotalRecords($query, string $primaryKey = '*'): int
{
if ($query instanceof QueryBuilder) {
$paginator = new Paginator($query->getQuery());
return count($paginator);
} else if ($query instanceof NativeQuery) {
$rsm = new ResultSetMappingBuilder($query->getEntityManager());
$rsm->addScalarResult('count', 'count');
$sqlCount = "select count(".$primaryKey.") as count from (" . $query->getSQL() . ") as item";
$count = $query->getEntityManager()->createNativeQuery($sqlCount, $rsm);
if ($query->getParameter('limit')) {
$query->setParameter('limit', null);
}
$count->setParameters($query->getParameters());
return (int)$count->getSingleScalarResult();
}
return 0;
}
If you have a dbal query builder that you have constructed with
$yourDbalQueryBuilder = $connection->createQueryBuilder();
then you can use:
$yourDbalQueryBuilder->setFirstResult(0)
->setMaxResults(100000000)
->execute()
->rowCount();

Function arguments, return all records from database table

I have a script that loops through and returns all records in the database table, code below.
PHP:
for($i=0;$i<$group_layer_row;$i++){
$my_layer_string="MyMap_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 0),"UTF-8","SJIS")."_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 1),"UTF-8","SJIS");
echo "var ".$my_layer_string.";\n";
}
What I am trying to do is turn this into an argument. Somewhat like this(this is an example, please don’t judge).
PHP:
function getLayers(){
$my_layer_string="MyMap_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 0),"UTF-8","SJIS")."_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 1),"UTF-8","SJIS");
$layers="var ".$my_layer_string.";\n";
echo $layers;
}
for($i=0;$i<$group_layer_row;$i++){
getLayers();
}
Any help on this would be very appreciated.
For reference I am including the sql query
$sql= "SELECT * FROM m_group_layer WHERE group_id=\"".$_SESSION["group_id"]."\" ORDER BY display_order";
$rs_group_layer= mssql_query ($sql, $con);
$group_layer_row =mssql_num_rows($rs_group_layer);
EDIT: This is almost the exact same loop just with different output.
for($i=0;$i<$group_layer_row;$i++){
$my_layer_string="MyMap_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 0),"UTF-8","SJIS")."_".mb_convert_encoding(mssql_result ($rs_group_layer, $i, 1),"UTF-8","SJIS");
echo "".$my_layer_string." = new OpenLayers.Layer.WMS( \"".$my_layer_string."\",\"http://192.0.0.0/cgi-bin/mapserv.exe?map=C:/ms4w/Apache/htdocs/mapserver/data/toyama/toyama_mymap.map&service=WMS&SRS=EPSG:2449&VERSION=1.1.1&format=image/PNG&layers=".$my_layer_string."\", {'layers': '".$my_layer_string."'}, {isBaseLayer: false, visibility: false,opacity:0.5,alpha:true});
map.addLayer(".$my_layer_string.");\n";
}
If I understand you correctly, this is a prime candidate for creating an object. As for making a function out of it, I think it's significantly cleaner to process the db resultset in a for loop. I don't see much benefit to creating a function (as passing the db result back and forth to a function inside a loop is very inefficient). Perhaps you might clarify your reasoning for wanting a function or what you are looking to accomplish?
BUT, if you really wanted to make a function out of it it would pretty much look how you outlined it ...
function getLayer($result_set, $row) {
$str = "MyMap_" . mb_convert_encoding(mssql_result($result_set, $i, 0),"UTF-8","SJIS")
$str .= "_".mb_convert_encoding(mssql_result($result_set, $i, 1),"UTF-8","SJIS");
return "var MyMap_$str;\n";
}
// $con = ...
$sql = "SELECT * FROM m_group_layer WHERE group_id=\"".$_SESSION["group_id"]."\" ORDER BY display_order";
$result = mssql_query ($sql, $con);
$row_count = mssql_num_rows($result);
for($i=0; $i<$row_count; $i++){
echo getLayer($result, $i);
}
UPDATE -- CLASS EXAMPLE
Okay, hopefully this doesn't scare you away. Everyone was afraid of OOP at some point. The important thing is to keep working at it and eventually you'll be like, 'OMG I <3 OOP LIKE GAGA LOVES HER LITTLE MONSTERS!!!'
I tried to document as much as possible. There's only so much you can explain without teaching a semester course :) How to use it is at the bottom of the code.
<?php
/**
* Retrieves layers from the db and provides methods for outputting
*/
class LayerMaker
{
/**
* Our MSSQL database connection
* #var MSSQL connection resource
*/
protected $db_conn;
/**
* Our array of records from the DB
* #var array
*/
protected $records;
/**
* Constructor function
*
* Called when you first instantiate the object. If you specify
* the db_conn, it will go ahead and retrieve the records as
* soon as the object is created.
*
* #param MSSQL connection resource $db_conn
*
* #return void
*/
public function __construct($db_conn=NULL)
{
if ($db_conn) {
$this->set_db_conn($db_conn);
$this->records = $this->query_db();
}
}
/**
* Setter function for protected $db_conn property
*
* You could just as easily create a method in the object
* to create the db connection, but for testing reasons that
* you likely don't care about it's better to inject the
* db connection into our object using a setter function like this.
*
* #param MSSQL link identifier $db_conn
*/
public function set_db_conn($db_conn)
{
$this->db_conn = $db_conn
}
/**
* How we get the records from the database into our object's $results property
*
* #return MSSQL record set on success or FALSE if no db connection is set
*/
protected function query_db()
{
// make sure we've set a database connection to use
// query the db and return the results
$sql = 'SELECT * FROM m_group_layer WHERE group_id="' .
$_SESSION["group_id"] . '" ORDER BY display_order';
return mssql_query($sql, $this->db_conn);
}
/**
* A function to get a count of the rows in our result set
*
* #return int Rows in the result property
*/
public function count_result_rows()
{
if ($this->records) {
return mssql_num_rows($this->records);
}
return 0;
}
/**
* Wrapper for mb_convert_encoding function
*
* #return string
*/
protected function layer_builder($row)
{
$str0 = mb_convert_encoding(mssql_result($this->records, $row, 0),"UTF-8","SJIS")
$str1 = mb_convert_encoding(mssql_result($this->records, $row, 1),"UTF-8","SJIS");
return "var MyMap_$str0_$str1";
}
/**
* Finally, build our layers!
*
* #param int $row Result set row number
*
* #return mixed Layer string if $row specified or Array of all layer strings
* if no specific row requested
*/
public function get_layers($row=NULL)
{
if ($row) {
// if we want one specific row ...
return $this->layer_builder($row);
} else {
// otherwise, give us back an array of all the rows
$layers = array();
for($i=0; $i<$this->count_result_rows(); $i++){
$layers[] = $this->layer_builder($i
}
return $layers;
}
}
/**
* Getter function for protected $records property
*
* Useful because you might want access to the resultset
* outside of the object context.
*
* #return array MSSQL record set
*/
public function get_records()
{
return $this->records;
}
}
// Now this is how you could use it
$conn = (however you retrieve a db connection);
$layer_obj = new LayerMaker($conn);
$layers = $layer_obj->get_layers();
print_r($layers);
?>

Categories