Use a DATE() function in a WHERE clause with DQL - doctrine

I get a strange error when I execute this DQL query:
SELECT u FROM User u LEFT JOIN u.schedule s WHERE DATE(s.timestamp) = DATE(NOW())
The exception is thrown by Doctrine with the message:
Expected known function, got 'DATE'
The problem looks similar to this bug, but that addresses the DATE() function in a GROUP BY clause and the bug is closed for Doctrine 2.2. At this moment, I get the exception with doctrine 2.4-DEV.
The query is meant to select all users scheduled for today. Is there any way I can create this DQL? I tested the SQL version in phpMyAdmin and there the query does not raise an error. What might be wrong?

You can achieve what you want by using a custom function:
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
class DateFunction extends FunctionNode
{
private $arg;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf('DATE(%s)', $this->arg->dispatch($sqlWalker));
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->arg = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
Then registering this function in your code:
$em->getConfiguration()->addCustomDatetimeFunction('DATE', 'DateFunction');
And your DQL query will work!

Related

Laravel - eager loading filtering related model on different database instance

i'm trying to implement filtering as defined here.
This works (to filter model A from controller A), however controller A/model A has a relation to model B, which is what I want to filter, as well as a 3rd relationship to model C from model B.
Model A is hosted on DB instance 1, Model B is hosted on DB instance 2 and are totally separated.
These relationships, without any filters, work fine.
Trying to mess around, I tried something like the below, which clearly does not work, however will hopefully illustrate what i'm trying to do. This filter gets applied to model A
protected function sn($sn)
{
$s= Bid::where('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
return $s;
}
SQLSTATE[42S22]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid column name 'crm'. (SQL: select * from [bids] where [crm] = (select * where [sNumber] = SN512345))
Bid is model A/controller A, CRM is model B which is the one I want to filter.
I thought about having numerous different functions in the model to filter, however I don't know if this was the best solution and I thought it was better to get it all out into another class.
I tried the below, which does not work as it applies the query to DB1.
$s= Bid::with('crm')->whereHas('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
[SQL Server]Invalid object name 'Opportunity'. (SQL: select * from [bids] where exists (select * from [Opportunity] where [bids].[crm_ref] = [Opportunity].[sNumber] and [sNumber] = SN512345))
Is there a way to implement this in some coherent and reusable way? I was thinking something along the lines of load Bid::, load CRM:: with applied filters, then append CRM:: to Bid:: somehow in the normal way that Eloquent would do this.
Thanks.
EDIT:
I am using the following filter in BidFilter.php
protected function sn($sn)
{
$users = DB::connection('sqlsrv_crm')->table('OpportunityBase')->select('*')->where('new_SalesNumber', '=', $sn)->get();
return $users;
}
And this filters the result set, as I can see in the debug bar:
debug bar queries
However this is also loading the normal unfiltered eager loaded CRM relationship. How can I switch to the filtered CRM results instead of the default unfiltered?
BidController index method:
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}
BidFilter
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}

How to fetch two related objects in Laravel (Eloquent) with one SQL query

I am trying to get two related objects in Laravel using eager loading as per documentation.
https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
My models are:
class Lead extends Model {
public function session() {
return $this->hasOne('App\LeadSession');
}
}
class LeadSession extends Model {
public function lead() {
return $this->belongsTo('App\Lead');
}
}
I want to get both objects with one SQL query. Basically I want to execute:
select * from lead_sessions as s
inner join lead as l
on l.id = s.lead_id
where s.token = '$token';
and then be able to access both the LeadSession and Lead objects. Here is the php code I am trying:
$lead = Lead::with(['session' => function ($q) use ($token) {
$q->where('token','=',$token);
}])->firstOrFail();
print($lead->session->id);
I have also tried:
$lead = Lead::whereHas('session', function($q) use ($token) {
$q->where('token','=',$token);
})->firstOrFail();
print($lead->session->id);
and
$session = LeadSession::with('lead')->where('token',$token)->firstOrFail();
print($session->lead->id);
In all three cases I get two queries executed, one for the leads table, and another for the lead_sessions table.
Is such a thing possible in Eloquent? In my view it should be a standard ORM operation, but for some reason I am struggling a whole day with it.
I don't want to use the Query Builder because I want to use the Eloquent objects and their functions afterwards.
I am coming from Python and Django and I want to replicate the behavior of select_related function in Django.
Try this and see if it makes more than one query
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->firstOrFail();
I hope it only runs a single query. I didnt test this. Not sure if you have to add a select() to pick the columns. But yeah, try this first.
Updates
Just adding how to use both session and lead data. Try a select and specify the data you need. The reason being that if both tables have similar columns like 'id', one of them will be overwritten. So you have to alias your select like
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->select(
'lead_sessions.*',
'leads.id as lead_id',
'leads.name',
'leads.more_stuff'
)
->firstOrFail();
Now all this data belongs to $session variable. For testing you were doing
print($lead->session->id);
//becomes
print($session->lead_id); //we aliased this in the query

Laravel - Eloquent Relationships (Query made; Not fetching value)

I am trying to get some database information, and I can see the query is being done, but I am unable to come up with content... I have the follows:
class Vendor extends Eloquent {
public function spider()
{
return $this->hasOne('Spider');
}
}
class Spider extends Eloquent {
public function report()
{
return $this->hasMany('Report');
}
}
So... now I am trying to use it as:
$vendor = Vendor::find(1);
$vendor->spider->report->id;
This is not giving me any result - the id is not being fetched. However, I can tell a correct query is being made to the database...
select * from `report` where `report`.`spider_id` = ?
Why is it not giving me anything? I have tried several ways such as... $vendor->spider->first()->report->first()->id... and so on... but all I get is errors.
Thank's.
Report is of type hasMany so a Collection will be retrieved. Process that Collection to find the instance you want.

How to fill a Symfony choice widget dynamically with the result of a DB query

In one of my forms I would like to have a dropdown (sfWidgetFormChoice) where the options are generated dynamically by executing a query on the database.
To be a little bit more precise, I'm going to list all versions which I have in the table. The query looks something like this:
select distinct version from mytable order by version desc
What I have so far but doesn't work:
class myForm extends sfForm
$query = "select distinct version from mytable order by version desc";
$versions = Doctrine_Manager::getInstance()->getCurrentConnection()->fetchAssoc($query);
public function configure()
$this->setWidgets(array('version' => new sfWidgetFormChoice(array('choices' => self::$versions))));
Edit:
Thansk guys for your answers! Much appreciated!
Anyhow your solutions base on having a model of the table. I'd rather have a PDO directly as it's faster.
In the Symfony Documentation I found what I was looking for under "Using Raw SQL Queries".
So I extended my Form to end up with this:
class myForm extends sfForm
{
public function getVersions()
{
$connection = Doctrine_Manager::connection();
$query = "select distinct version from mytable order by version desc";
$statement = $connection->prepare($query);
$statement->execute();
$resultset = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
return $resultset;
}
public function configure()
{
$this->setWidgets(array('version' => new sfWidgetFormChoice(array('choices' => self::getVersions()))));
}
}
As a result of this my dropdown gets properly filled with what is in my table, yay! BUT I get also warnings:
Warning: Invalid argument supplied for foreach() in lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/database/sfDoctrineConnectionProfiler.class.php on line 196
Warning: join() [function.join]: Invalid arguments passed in lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/database/sfDoctrineConnectionProfiler.class.php on line 141
Warning: Invalid argument supplied for foreach() in lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/database/sfDoctrineConnectionProfiler.class.php on line 196
Warning: Invalid argument supplied for foreach() in lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/debug/sfDoctrineConnectionProfiler.class.php on line 117
Any idea what I'm doing wrong here??? Oddly enough the dropdown looks fine.
Use the table_method option of sfWidgetFormDoctrineChoice like this :
In your form class do this :
$this->setWidgets(array('version' => new sfWidgetDoctrineChoice(array('model' => 'Version', 'table_method' => 'getData')));
then in the versionTable.class.php file create a getData() (you can call this anything) function that returns a collection of objects :
public function getData() {
$this->getInstance()->createQuery()
->orderBy('version desc')
->execute();
}
You can use sfWidgetFormDoctrineChoice
public function configure(){
$this->widgetSchema['version'] = new sfWidgetFormDoctrineChoice(array('model' => 'YourModel', 'query' => Doctrine::getTable('YurModel')->getYourDataQuery(), 'add_empty' => true));
}
And in your modelTable.class.php
public function getYourDataQuery()
{
//Your query
$q = $this->createQuery('a');
return $q;
}
I finally found the solution I was looking for by myself. This accesses the DB now directly. The reason why I don't use FETCH_COLUMN anymore is that this would create an array where the key is an Id starting from 0 and the value is whatever is returned by the query itself. Instead I wanted to have the key and value to be whatever the query delivers. This is why the query now delivers two columns being the same and then FETCH_KEY_PAIR does the rest.
In case anyone else is interested in how I solved this, here is my code which is filling the drop down properly and causing no errors.
class myForm extends sfForm
{
public function getVersions()
{
$connection = Doctrine_Manager::getInstance()->getCurrentConnection()->getDBh();
$query = "select distinct version as key, version as value from mytable order by version desc";
$statement = $connection->prepare($query);
$statement->execute();
$resultset = $statement->fetchAll(PDO::FETCH_KEY_PAIR);
return $resultset;
}
public function configure()
{
$this->setWidgets(array('version' => new sfWidgetFormChoice(array('choices' => self::getVersions()))));
}
}
Thank you sir, I really appreciate your solution.
you have saved my day!
Just in case anyone who's having similar issues.
I ran into a PDO not found problem when using the solution.
(FatalErrorException: Error: Class '.......\Controller\PDO' not found in )
Reason being:
https://groups.google.com/forum/#!topic/symfony2/mR22XXKPQrk
"You are misusing the PHP namespaces. When you are in a namespaced code,
using PDO means a class name relative to the current namespace. You have
2 solutions to fix your issue: using a fully qualified class name like
\PDO or adding a use statement to import the class in the current namespace."
So After adding the '\' in front of PDO::, the solution works perfectly.
Thanks!

Exception with Doctrine2 and HYDRATE_SIMPLEOBJECT

As a good practice i'm tring to hydrate an object as small as possible since data is going to be read only (just show the entity in my Twig template). So i've tried HYDRATE_SIMPLEOBJECT hydratation mode but i'm getting this exception:
Cannot use SimpleObjectHydrator with a ResultSetMapping that contains
more than one object result.
How should i interpret this message? By the way, here is the code that throws the exception:
protected function getFindAllQueryBuilder()
{
return $this->createQueryBuilder('p')
->select(array('p', 'parent', 'features', 'users'))
->leftJoin('p.parent', 'parent')
->leftJoin('p.features', 'features')
->leftJoin('p.users', 'users');
}
public function findOneBySlugAsObject($slug)
{
$qb = $this->getFindAllQueryBuilder();
return $qb
->where($qb->expr()->eq('p.slug', ':slug'))
->setParameter('slug', $slug)
->getQuery()->getOneOrNullResult(Query::HYDRATE_SIMPLEOBJECT);
}
SimpleObjectHydrator is for result sets where you don't use any fetch joins in your query, in other words you can't use it if you use more than one alias in your select. SimpleObjectHydrator is faster because doesn't handle these fetch joins.

Resources