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'));
Related
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.
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')
Hello I'm using phalcon Framework.
I need to make a find, normally I would use distinct, but I not found an option.
My class as below :
class Calls extends \Phalcon\Mvc\Collection {
public $agent;
public $number;
public $date;
public $status;
}
I need to use distinct fields date and number ?
you should use query builder:
Basic implementation for later example:
$queryBuilder = $this->getDI()->getModelsManager()
->createBuilder()
->addFrom('tableName', 't');
Distinct command:
$queryBuilder->distinct('t.id');
Column thing works too, but not recommended:
$queryBuilder->columns('DISTINCT(t.id) AS id')
Using:
Symfony 2.5
SonataAdminBundle
I am trying to change one of the entity fields (title) when data is submitted / saved to database by using two fields from associated entites ex.
DocumentRevision <- Document -> CustomEntity [title] = Document[title]+DocumentRevision[number]
But title of CustomEntity has to be unique - this was the problem I was trying to solve and managed with Database constraints and UniqueEntity validation (not quite - more on this later).
Now the issue is that I change the title data on Doctrine preUpdate/Persist effectivly skipping validation for that field since it's empty at validation time. When user puts wrong data Database layer throws an error about duplicate for unique constraint.
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function setTitleFromDocumentName() {
$this->setTitle($this->getDocument()->getName() . " rev. " . $this->getDocumentRevision()->getRevisionNumber());
}
The entity itself is using UniqueEntity constraint on field title, so custom constraints or validation groups are pointles from my perspective as it would only duplicate the already used constraint.
/**
* #UniqueEntity(
* fields={"title"}
* )
**/
The simplest solution as it seems would be to get somewhere between post Submit before validation, but it would have to be done from Entity.
My question is how can (can it?) be done without overriding SonataCRUD Controller or it's other parts, is it even possible?
It can be done, but there are issues:
I was able to change the title using Form Events like this:
protected function configureFormFields(FormMapper $formMapper) {
...
$builder = $formMapper->getFormBuilder();
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (!$data) {
return;
}
$data['title'] = $data['document'] . ' rev. ' . $data['documentRevision'];
$event->setData($data);
}
...
formMapper
->add('title',null,array(
...
);
The current problem is that I am getting the IDs of 'document' and 'documentRevision' and I need their names or __toString() representation at least.
Another issue is that although I can set the title using the event it shows error from DB when it should show Form error since validation should be done on FormEvents::SUBMIT - this one I don't understand.
Last thing to note is that if I try to use callback function:
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this,'onPreSubmit'))
public function onPreSubmit() {
$entity = $this->getSubject();
$entity->setTitleFromDocumentName();
}
I will get null title and errors if Entity tries to get fields from related entites - Calling function on non object.
Regarding entity data maybe this will help you to get the subject:
https://gist.github.com/webdevilopers/fef9e296e77bb879d138
Then you could use getters to get the desired data for instance:
protected function configureFormFields(FormMapper $formMapper)
{
$subject = $this->getSubject();
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($subject) {
$document = $subject->getDocument();
// ...
});
}
I also posted this on your issue:
https://github.com/sonata-project/SonataAdminBundle/issues/2273
To solved this when I changed the unique entity validation constraints as ones used by me where not completely valid from conceptual perspective.
Also it's important to note that functions that are marked as #PrePersist, #PreUpdate etc. must be public if they are to be used like that, marking them private will make Doctrine fail.
Note that the methods set as lifecycle callbacks need to be public and, when using these annotations, you have to apply the #HasLifecycleCallbacks marker annotation on the entity class.
See: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#lifecycle-callbacks (first paragraph after the code sample).
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;
}