We are working with Symfony and Doctrine ORM. We mostly use MySQL / MariaDB, and for a new project we are considering trying PostgreSQL.
Obviously using an ORM like Doctrine is doing a lot of the work for us, and 99% of our requests are fine.
I'm stuck with a specific request, when I'd like to filter our users by their role. We normally do something like this, using LIKE:
public function findByRole(string $role): array
{
return $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"'.$role.'"%')
->orderBy('u.id', 'ASC')
->getQuery()
->getResult()
;
}
This is unfortunately not working with postgresql, and after searching on this forum I found a solution using JSON_GET_TEXT. But my problem is that we have to give the index of the array to search in, and I don't know where my role could be.
So I have this somewhat working function right now, but it is so ugly :
/**
* Search all the users having a specific role.
*
* #param string $role - The role to search for in user's roles
*
* #return mixed
*/
public function findByRole(string $role): mixed
{
// TODO: Il y a probablement mieux à faire...
return $this->createQueryBuilder('u')
->andWhere('JSON_GET_TEXT(u.roles,0) = :role')
->orWhere('JSON_GET_TEXT(u.roles,1) = :role')
->orWhere('JSON_GET_TEXT(u.roles,2) = :role')
->orWhere('JSON_GET_TEXT(u.roles,3) = :role')
->orWhere('JSON_GET_TEXT(u.roles,4) = :role')
->orWhere('JSON_GET_TEXT(u.roles,5) = :role')
->orWhere('JSON_GET_TEXT(u.roles,6) = :role')
->orWhere('JSON_GET_TEXT(u.roles,7) = :role')
->orWhere('JSON_GET_TEXT(u.roles,8) = :role')
->orWhere('JSON_GET_TEXT(u.roles,9) = :role')
->setParameter('role', $role)
->getQuery()
->getResult();
}
How can I improve my function to work on postgresql? And more importantly, is there a solution that could work on both MySQL and PostgreSQL with Doctrine so we could decide to switch seamlessly between the two database engines?
I don't know if my answer is related with your problem.
In PostgreSQL "user" is a reserved keyword, so you can't create database named USER in postgre. But Symfony found a solution and switchs the name directly to 'user' with parentheses.
What I would like to say is that you may face problems while using 'User' name in postgre so it's recomended to name your table 'account' instead of 'user' here.
Related
I'm trying to create a relation to a postgres array column in Yii2 and it's giving me an error (not surprisingly)
SQLSTATE[42883]: Undefined function: 7 ERROR: operator does not exist: integer[] = integer
Just setting the standard onCondition() doesn't seem to work.
Anyone have experience working with postgres array types and Yii2 relations? It would be nice if I could do something like this to override the default operator and on condition to support array column type.
/**
* #return \yii\db\ActiveQuery
*/
public function getMyRelation()
{
return $this->hasMany(ModelName::className(), ['#>', 'id', '{'.intval($this->rel_id).'}'])->alias('myRelation');
}
You cannot create hasMany relation in this way - ActiveRecord does not support such syntax and you cannot use model attributes for defining relations, since this method may be executed before real model initialization (for example if you're building joins).
You may create getter which will use ActiveQuery to obtain related models - it will not be real relation and you will not be able to use it for eager loading or joins, but should work fine with lazy loading for single model:
public function getMyRelation() {
$query = ModelName::find()
->andWhere(['#>', 'id', '{'. (int) $this->rel_id .'}'])
->alias('myRelation');
$query->primaryModel = $this;
$query->multiple = true;
return $query;
}
I have Eloquent Event model, which is related towards multiple dates like this:
$event->dates // shows Collection of 8 Eloquent date models
After that i need to pick the only date, what is closest to current time. I know how to do this using query of raw SQL, or DB class. But isnt there any better solution? I dont want to jump into database for data, I already have.
Date format in eloquent models is surprisingly string.
You can use what we call in laravel mutators like this ->
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Event extends Model
{
public function dates()
{
return $this->hasMany('Date');
}
/**
* Get Dates for the event.
*
* #param string $value
* #return array
*/
public function getDates()
{
$dates = $this->dates()->getQuery()->orderBy('created_at', 'asc')->get();
return $dates;
}
}
Hope this helps.
UPDATE
I think now you can also directly do this in the model definition like this -
return $this->hasMany('Date')->orderBy('created_at', 'asc')
I need to get all rows where DATE(a.when) matches the string 2014-09-30.
$builder = $this->em->createQueryBuilder();
$builder->select('a')
->from('Entity\Appointment', 'a')
->andWhere('a.when = :date')
->setParameter('date', $date);
a.when is a full DATETIME; :date is only a string (in DATE format).
The following and variations didn't work:
->andWhere('DATE(a.when) = :date')
Error: Expected known function, got 'DATE'
What's the correct usage here?
Thanks to andy, using this now:
$builder = $this->em->createQueryBuilder();
$builder->select('a')
->from('Entity\Appointment', 'a')
->andWhere('a.when >= :date_start')
->andWhere('a.when <= :date_end')
->setParameter('date_start', $date->format('Y-m-d 00:00:00'))
->setParameter('date_end', $date->format('Y-m-d 23:59:59'));
This actually is a very common question.
It turns out that not all sql databases support a DATE function, so the good people in charge of Doctrine decided not to support it nativelly.
Kind of wish they did because it would have saved a bunch of people a fair amount of effort.
So add this rather magical class to your project:
namespace Cerad\Bundle\CoreBundle\Doctrine\DQL;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
class Date extends FunctionNode
{
public $date;
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return "DATE(" . $sqlWalker->walkArithmeticPrimary($this->date) . ")";
}
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->date = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
Then wire it up in the doctrine section of your app/config.yml:
doctrine:
orm:
default_entity_manager: default
auto_generate_proxy_classes: %kernel.debug%
entity_managers:
default:
connection: default
...
dql:
datetime_functions:
date: Cerad\Bundle\CoreBundle\Doctrine\DQL\Date
http://doctrine-orm.readthedocs.org/en/latest/cookbook/dql-user-defined-functions.html
http://symfony.com/doc/current/cookbook/doctrine/custom_dql_functions.html
http://symfony.com/doc/current/reference/configuration/doctrine.html
There are other bundles out there with more sql functions. Oddly enough, the first time I looked a few years ago, none of them had Date defined. So I just made my own.
====================================================================
Update 01
I did not check the tags carefully and assumed that this was a Symfony 2 application. The Date class stays the same. You wire it up by getting the doctrine configuration object.
$config = new \Doctrine\ORM\Configuration();
$config->addCustomDatetimeFunction('DATE', 'blahblah\Date');
Check the Doctrine link for details.
A different approach using $qb->expr()->between() in the same andWhere:
$builder = $this->em->createQueryBuilder(); $builder->select('a')
->from('Entity\Appointment', 'a')
->andWhere($qb->expr()->between('a.when', ':date_start', ':date_end'))
->setParameter('date_start', $date->format('Y-m-d 00:00:00'))
->setParameter('date_end', $date->format('Y-m-d 23:59:59'));
I'm using Postgres with Kohana 3's ORM module and would like to run a SELECT using a postgres function to convert values already in the database to lower case before doing the comparison.
In SQL I would write:
select * from accounts where lower(email) = 'alice#spam.com';
In Kohana I would like to write something like this:
$user = ORM::factory('user')
->where('lower(email)', '=', strtolower('alice#spam.com'))
->find();
But this gives an error because ORM is trying to deduce the column name as 'lower(email)' rather than just 'email'.
I'm new to Kohana and ORM so alternatives that would give me the same result would be useful too.
Or IMHO even beter, try this:
$user = ORM::factory('user')
->where('LOWER("email")', '=', DB::expr("LOWER('alice#spam.com')"))
->find();
PS. I do not see any need to create a DB::lower() helper, but that might just be me...
EDIT:
$value = 'alice#spam.com';
$user = ORM::factory('user')
->where('LOWER("email")', '= LOWER', (array) $value)
->find();
The query will become something like (havent used ORM in a while) "SELECT users.id, users.email FROM users WHERE LOWER("email") = LOWER ('alice#spam.com') LIMIT 1".
Notice the space, I just updated some of my code to use this since I just figured out this posibility.
I hope you will be as happy with it as I am.
try this:
$user = ORM::factory('user')
->where(DB::expr('lower(email)'), '=', strtolower('alice#spam.com'))
->find();
I'm not completely happy with the use of a helper but I use it a couple other classes so it's nice to keep the logic in one location. Here is what I'm currently using.
class DB extends Kohana_DB
{
public static function lower($value)
{
return DB::expr('lower('.Database::instance()->quote($value).')');
}
}
class Model_User extends Model_Base
{
public static function find_by_email($email)
{
$user = ORM::factory('user')
->where(DB::expr('lower(email)'), '=', DB::lower($email))
->find();
return $user;
}
I am new to zend. I have been asked to redevelop a website that was once written in plain PHP and put it into the zend framework.
I am having a lot of trouble with database relationships, I cant seem to get my head round defining and querying relationships.
I would like to find a Category. From that Category I would like to be able to find all the CategoryInfo associated with it, and be able to query/sort/limit that dataset.
Here are my models.
Categorys.php
<?php
class Default_Model_Categorys extends Zend_Db_Table_Abstract
{
protected $_name = 'Categorys';
protected $_primary = 'id';
protected $_dependentTables = array('Default_Model_CategoryInfo');
}
?>
CategoryInfo.php
<?php
class Default_Model_CategoryInfo extends Zend_Db_Table_Abstract
{
protected $_name = 'Category_Info';
protected $_primary = 'id';
protected $_referenceMap = array(
'Categorys' => array(
'columns' => array('cat_id'),
'refTableClass' => 'Default_Model_Categorys',
'refColumns' => array('id')
)
);
}
?>
CategoryController.php
<?php
class CategorysController extends Zend_Controller_Action
{
public function indexAction()
{
/*
this should redirect to all games
*/
return $this->_forward("index", "games");
}
public function categoryAction()
{
/*
shows a specific category
*/
$id = (int) $this->_request->getParam('id');
$category = new Default_Model_Categorys();
$this->view->category = $category->fetchRow(
$category->select()->where('id = ?', $id)
);
$categoryInfo = $this->view->category->findDependentRowset('Default_Model_CategoryInfo');
}
}
Firstly... am I doing anything wrong?
Secondly... how do I go about querying the dependent rowset?
First, if you're searching for a category by its primary key, it's simpler to use the find() method:
$id = (int) $this->_request->getParam('id');
$category = new Default_Model_Categorys();
$this->view->category = $category->find($id)->current();
Second, to restrict or sort dependent Category_Info rows, you can use a Zend_Db_Table_Select object as an optional parameter of findDependentRowset(). Here's an example:
$select = $category->select()->where("info_type = 'PRICE'")
->order("info_date")
->limit(3);
$categoryInfo = $this->view->category->findDependentRowset(
'Default_Model_CategoryInfo', null, $select);
Notice you can use any table object to create that select object. Since the "FROM" clause for that select will be set by the findDependentRowset() method, you just add other clauses and then pass it in.
PS: You don't need to declare $_dependentTables at all, unless you're going to use cascading update or cascading delete via PHP code. I recommend strongly against doing that -- it's far more efficient to let the RDBMS handle those cascading operations.
Likewise you should never have to declare $_primary if your database tables actually declare primary key constraints. Zend_Db_Table_Abstract knows how to inspect metadata to get the primary key column(s).
Everything looks correctly to me. You don't query a dependent rowset. It is a query itself and it returns a result set. Basically what it is doing is pulling all records related to the current row you are working with as defined by $_referenceMap. Once you execute findDependentRowset(), you can foreach over the results which will give you instances of Zend_Db_Table_Row. From there you can display the related data as needed.
Personally I don't use Zend_Db Relationships. It is much easier to just make a second model method to query what I need. Also, Zend_Db Relationships do not support where clauses, so just making a second query is much more flexible than relationships.