Is there any advantages of using Laravel Eloquent all the time instead of raw SQL?
I have a habit of writing SQL first in phpMyAdmin to check the relationship and then translate it Eloquent ORM.
Sometime translating to Eloquent ORM is painful and time consuming especially translating from long complicated SQL query. I am able to write fast in SQL than using Eloquent ORM.
The most important benefit of using the Query Builder is abstraction, which usually leads to less code. Also, because the builder is database agnostic, it allows to seamlessly switch the RDBMS, for example from MySQL to PostgreSQL (but this only applies in some cases as there are some things that are database specific and cannot be abstracted).
Using it in conjunction with Eloquent offers the added benefit of having the results converted into Eloquent models, which means you can use relations, mutators, accessors and all the other benefits that Eloquent models offer. For example:
$users = DB::select('select * from users');
Will return an array of stdClass objects, while the following:
$users = User::all();
Will return a collection of Eloquent models on which you can get relations:
foreach ($users as $user) {
$user->projects();
}
Or make changes and save the entry:
$user->name = 'Bob Dylan';
$user->save();
These things can be done with the raw query approach, by manually creating a collection of models like so:
// Replace the stdClass items with User models
foreach ($users as &$user) {
$user = new User($user);
}
// Create a Collection with the results
$users = new Illuminate\Support\Collection($users);
But it adds complexity which is already implemented by Eloquent.
A good example of how abstraction leads to less code would be this:
User::whereIn('id', [1, 7, 100]);
Which is less code than the equivalent:
DB::select('select * from users where id in (?)', [implode(',', [1, 7, 100]);
The important thing to take in consideration when using raw queries that make use of user input, is to always use bindings to avoid leaving yourself open to SQL Injection.
That being said, there are cases where, as you said, it's a pain to convert queries to use the Query Builder, because the builder has limitations given that it's database agnostic.
There is no problem with using raw queries. I usually use a combination of both Query Builder for simpler queries that need Eloquent models and raw queries for more complex operations, where it just makes sense not to use DB::raw allover the place just to use the Query Builder. So taking into account the things I said above, it just boils down to preference really.
There's no problem and you should use raw SQL if that suits you better. Eloquent's goal is to simplify queries.
The only thing you have to take care of is to prepare everything.
Related
I'd like to make a MySQL query with doctrine2 QueryBuilder (Symfony 3.4 app) with a NOT BETWEEN statment.
The doctrine provide a ->expr()->between(..) but not ->expr()->notBetween(..)
Is there a way to negate the between with the query builder.
I don't want to use a native or a DQL query if possible.
Note: I think a possible solution is to use ->expr()->lt(..) and/or ->expr()->gt(..) but I want to know if notBetween is possible.
Thanks
Expected:
A NOT BETWEEN SQL statement with Doctrine2 QueryBuilder
After some attempts, I found a suitable solution for me:
The QueryBuilder provide a ->expr()->not(...), so in this case this is possible:
$qb->exp()->not($qb->between('field', 'from', 'to')
SQL result:
NOT (BETWEEN field from AND TO)
I just started studying Symfony 3.4 and Doctrine ORM. I need to take count of users. I tested with the normal query it is working fine. How can I convert the normal query into Doctrine ORM? Any help would be appreciated.
"SELECT count(DATE_FORMAT(application.reviewed_at, '%Y-%m-%d')) AS count, user_id FROM application WHERE user_id = 6 AND DATE(reviewed_at) = CURDATE() GROUP BY DATE(reviewed_at)";
I tried below code. It is not working.
$qb = $em->createQueryBuilder();
$entries = $qb->select("count(DATE_FORMAT(application.reviewed_at, '%Y-%m-%d')) AS count", "user_id")
->where("user_id = 6", "DATE(reviewed_at) = CURDATE()")
->from('AppBundle\Entity\Application', 'u');
->groupBy("DATE(reviewed_at)");
->getQuery()
->getResult();
Simply put, Doctrine ORM is not intended to transform SQL queries back into DQL. DQL is a query language that can be translated into multiple vendor-specific SQL variants, so this is a one-way transformation from DQL to SQL.
Thus, if you have just a SQL query, it could be a good idea to use just that, using Doctrine DBAL. Of course, you'd probably have to deal with result set mappings, but there could be no need to drag all the ORM stuff into your code.
What you're doing here is essentially building a DQL query using the Doctrine's QueryBuilder API. The FROM part is absolutely correct, but those using the DATE_FORMAT and DATE functions look wrong to me. Since DQL translates to several different SQL implementations, depending on the driver you're using, it needs a compatibility layer because functions such as DATE_FORMAT, DATE and CURDATE are not common for all the platforms.
First, take a look at the documentation for user-defined functions. For example, the DATEDIFF() function in MySQL is a good example. If it turns out that no one has already implemented support for the functions you're using, that's a good place to start, since it basically describes how to implement an extension for the Doctrine Query Language (DQL).
Second, there's a good chance that the functions you're using are already implemented in some of the third-party libraries, such as OroCRM's or Benjamin Eberlei's Doctrine extension collections.
Also, if you're selecting an aggregation or a function call result, it's always a good idea to alias it. What you could try here is use the $qb->expr() function subset to limit the DQL to a more strict grammar.
It is important to remember that Doctrine DQL is not an SQL dialect. It is a custom language that attempts to look close to SQL, but it operates with Doctrine entities and associations, not with database tables and columns. Of course at the end it transforms into SQL, but until this transformation you need to stay within DQL syntax.
One of reasons why your query did not work is an attempt to use native SQL (MySQL to be true) functions inside DQL query. DQL have no such functions and you have 2 ways to go at this point:
Rewrite your query in a way that it will be compatible with Doctrine. It may involve updates for your database scheme. In your particular case you may want to convert reviewet_at column from whatever it is now (timestamp I suppose) into date type that allows direct date comparisons. It will let you to get rid of custom MySQL date transformation functions and will make your query compatible with Doctrine
You can choose a harder path and implement all necessary custom functions directly in Doctrine. It is not a simple thing, but Doctrine is flexible enough to provide you with such ability. There is good article into Doctrine documentation about this topic if you will be interested.
Try to give a go to this:
public function select()
{
$queryBuilder = $this->createQueryBuilder('a');
$queryBuilder
->addSelect(['a.user_id', $queryBuilder->expr()->count('a.reviewed_at')])
->where('a.user_id = :idUser')
->andWhere('a.reviewed_at = :date')
->setParameters([
'idUser' => 6,
'date' => new \DateTime(),
])
->groupBy('a.reviewed_at')
;
return $queryBuilder
->getQuery()
->getResult()
;
}
As far as I can see, the only way to make a query to ElasticSearch in Yii2 is to run ElasticModel::find()->query($query), where $query is a complex array containing the actual query written in ElasticSearch query DSL.
The query is huge and quickly becomes unmanageable. For SQL Yii2 provides a powerful query builder class that supports tons of useful methods like andWhere(). For ElasticSearch everything goes into one gigantic query expression, very much like building an SQL expression string by hand.
Is there any high-level wrapper for ElasticSearch query DSL for Yii2? If not, is there a standalone library with similar functionality?
If you intend to build for version 1.6 of elastic, I have created a query builder for my company and published it here
You will use it as a standalone query builder, and at the end you will need to get the final query array and pass it to the query executer.
To install it, you can simply use composer composer require itvisionsy/php-es-orm or download the zipped version here.
The link above contains some examples, and here is a copy:
//build the query using different methods
$query = \ItvisionSy\EsMapper\QueryBuilder::make()
->where('key1','some value') //term clause
->where('key2',$intValue,'>') //range clause
->where('key3','value','!=') //must_not term clause
->where('key4', ['value1','value2']) //terms clause
->where('email', '#hotmail.com', '*=') //wildcard search for all #hotmail.com emails
->sort('key1','asc') //first sort option
->sort('key2',['order'=>'asc','mode'=>'avg']) //second sort option
->from(20)->size(20) //results from 20 to 39
->toArray();
//modify the query as you need
$query['aggs']=['company'=>['terms'=>['field'=>'company']]];
//then execute it against a type query
$result = TypeQuery::query($query);
//i am not sure about Yii way to execute, according to the question, it should be:
$result = ElasticModel::find()->query($query);
The package also include a simple ElasticSearch ORM class which maybe useful for you. Take a look at it here.
Hope this helps you...
Reading the Laravel documentation I see that:
Note: The Laravel query builder uses PDO parameter binding throughout to protect your application against SQL injection attacks. There is no need to clean strings being passed as bindings.
Does that still apply if I only craft queries in the following manner?
DB::query("SELECT * from table WHERE id like " . $id);
Let's take that sentence and emphasise the key phrase:
There is no need to clean strings being passed as bindings.
In your example, $id is not being passed as a binding, it is just being injected into the raw SQL, so it is not protected.
You should follow standard practice for preventing SQL injection:
in cases like this, where the input is always an integer, you could use intval($id)
you could get the underlying PDO object with DB::getPdo()/DB::getReadPdo() and use PDO::quote() to correctly escape strings
although the documentation is rather poor, Laravel's DB facade can run fully parameterised queries, such as DB::select('SELECT * FROM users WHERE users.id = ?', array($userId));
Parameterised queries are usually considered the gold standard in injection prevention, and are what Eloquent is using internally when you use the query builder. The idea is that you first give the database (or, at minimum, the database driver) the complete query with no user input at all, so there is no doubt which tables and columns should be in use. You then pass in the user input as completely separate data, which is never actually written into the SQL, just applied to the query you already sent.
Parameterised queries can't do everything for you, though - for instance, most libraries, including PDO, can't bind a table or column name as a parameter. That's because it will actually create a different query every time it is run, negating the separation between query and data. If you want to do that, you therefore need some other method of ensuring safety - usually, a whitelist of allowed values is the best idea.
No, DB::query() is not part of the concept of Query Builder. Instead, this will be protected:
DB::table('table')->where('id', 'like', $id)->get();
But the best way to protect your queries is to use Query Builder at its max:
DB::table('table')->where('id', 'like', $id)->get();
Another way to protect your queries, if you really are forced to write a raw query, is to cast your data to the type they are supposed to be:
DB::query(DB::raw("SELECT * from table WHERE id like " . (int) $id));
In this case if $id is 'some exploit' the query will be:
SELECT * from table WHERE id like 0
In Query Builder you can also pass your parameters (bindings) like this to harden the security of your queries:
DB::select('SELECT * FROM users WHERE users.id = ?', array($userId));
The problem is next - I want to execute simple query (e.g. 10 rows from one table)
In Doctrine this operation takes 0.013752s
Here is DQL:
$q = Doctrine_Query::create()
->update('TABLE')
->set('FIELD', 1)
->where('ID = ?', $id);
$rows = $q->execute();
But when i use plain sql and mysql_query() it takes only 0.003298s
What's wrong? Is Doctrine realy 4x slower?
John,
Nothing is wrong. Doctrine introduces considerable overhead compared to a straight SQL query. But you gain the convenience of a nice object oriented interface to the database as well as many other benefits. If raw performance is really important then you might not want to use Doctrine.
For queries where I need performance over convenience (hundreds of thousands of inserts for example) I use PDO to avoid the overhead that gets introduced by the ORM.