Is there a way to hint the use of an index in Doctrine2? The equivalent of the MySQL USE INDEX syntax:
SELECT * FROM user u USE INDEX(my_super_index) ...
I found a gist with working code using a custom tree walker: https://gist.github.com/arnaud-lb/2704404
Thanks to the author!
It will not work for RDBMS using a different syntax than MySQL though.
Update: The previous code does not work for queries with multiple tables in the FROM clause. So here is my updated version of the previous walker:
<?php
namespace __YOUR_NAMESPACE_;
use Doctrine\ORM\Query\SqlWalker;
class UseIndexWalker extends SqlWalker
{
const HINT_USE_INDEX = 'UseIndexWalker.UseIndex';
public function walkFromClause($fromClause)
{
$sql = parent::walkFromClause($fromClause);
$index = $this->getQuery()->getHint(self::HINT_USE_INDEX);
return preg_replace('/( INNER JOIN| LEFT JOIN|$)/', sprintf(' USE INDEX(%s)\1', $index), $sql, 1);
}
}
Notice that when you implements this on a Query in a repository in Symfony, you have to set FDQN for the value of the Hint Query::HINT_CUSTOM_OUTPUT_WALKER
<?php
namespace __YOUR_NAMESPACE_\Repository;
use Doctrine\ORM\Query;
use __YOUR_NAMESPACE_\UseIndexWalker;
class MyEntityRepository extends EntityRepository
{
public function myFunction()
{
$query = $this->entityManager->createQueryBuilder()->select('my_field')->from('my_entity', 'my_entity')->getQuery();
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, UseIndexWalker::class);
$query->setHint(UseIndexWalker::HINT_USE_INDEX, 'my_super_index');
}
}
Related
I have a question about extending my own Models eloquent.
In the project I am currently working on is table called modules and it contains list of project modules, number of elements of that module, add date etc.
For example:
id = 1; name = 'users'; count = 120; date_add = '2007-05-05';
and this entity called users corresponds to model User (Table - users) so that "count" it's number of Users
and to update count we use script running every day (I know that it's not good way but... u know).
In that script is loop and inside that loop a lot of if statement (1 per module) and inside the if a single query with count. According to example it's similar to:
foreach($modules as $module) {
if($module['name'] == 'users') {
$count = old_and_bad_method_to_count('users', "state = 'on'");
}
}
function old_and_bad_method_to_count($table, $sql_cond) {}
So its look terrible.
I need to refactor that code a little bit, because it's use a dangerous function instead of Query/Builder or Eloquent/Model and looks bad.
I came up with an idea that I will use a Models and create Interface ElementsCountable and all models that do not have an interface will use the Model::all()->count(), and those with an interface will use the interface method:
foreach ($modules as $module) {
$className = $module->getModelName();
if($className) {
$modelInterfaces = class_implements($className);
if(isset($modelInterfaces[ElementsCountable::class])) {
/** #var ElementsCountable $className */
$count = $className::countModuleElements();
} else {
/** #var Model $className */
$count = $className::all()->count();
}
}
}
in method getModelName() i use a const map array (table -> model) which I created, because a lot of models have custom table name.
But then I realize that will be a good way, but there is a few records in Modules that use the same table, for example users_off which use the same table as users, but use other condition - state = 'off'
So it complicated things a little bit, and there is a right question: There is a good way to extends User and add scope with condition on boot?
class UserOff extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(function (Builder $builder) {
$builder->where('state', '=', 'off');
});
}
}
Because I have some concerns if this is a good solution. Because all method of that class NEED always that scope and how to prevent from method withoutGlobalScope() and what about other complications?
I think it's a good solution to create the UserOff model with the additional global scope for this purpose.
I also think the solution I would want to implement would allow me to do something like
$count = $modules->sum(function ($module) {
$className = $module->getModelName();
return $className::modulesCount();
}
I would create an interface ModulesCountable that mandates a modulesCount() method on each of the models. The modulesCount() method would return either the default count or whatever current implementation you have in countModuleElements().
If there are a lot of models I would probably use a trait DefaultModulesCount for the default count, and maybe the custom version too eg. ElementsModuleCount if that is consistent.
I hope there are a few helpful and prefer German Symfony experts. For many years I have been working with PHP and now I tryed in a framework. I chose Symfony now because I like the components the most. The QueryBuilder and I stand on a war foot - I just do not understand it. Storing values works very well so far, though I doubt that I'm doing this in the sense of the framework. I'm really helpless. Currently I managing it by chasing everything in raw format but I'm not really happy with it.
How I can implement the following with Doctrine?
use App\Entity\Questions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManager;
class QuestionsController extends AbstractController
{
/**
* #Route("/addquestion", methods={"POST","HEAD"})
*/
public function addQuestion()
{
$entityManager = $this->getDoctrine()->getManager();
$RAW_QUERY = 'SELECT COUNT(*) AS c FROM questions WHERE questToID = '.$_POST['u'].' AND questFrom = '.$this->getUser()->getId().';';
$statement = $entityManager->getConnection()->prepare($RAW_QUERY);
$statement->execute();
$total = $statement->fetchAll();
if($total[0]['c'] >= 3)
{
return new Response('error|Du kannst der selben Person maximal drei Fragen stellen.');
}
[...]
I have already tried to implement this, and many other things (no avail):
Count Rows in Doctrine QueryBuilder
Since I speak bad English, I very much hope that you understand me…
so getting the value with $_POST['u'] is not recommended her you can either pass the value with the route an retrieve it as a parameter for the function:
/**
* #Route("/addquestion/{u}", methods={"POST","HEAD"})
*/
public function addQuestion($u)
or you can get it out from the request it self:
/**
* #Route("/addquestion", methods={"POST","HEAD"})
*/
public function addQuestion(Request $request)
if you want to use the query builder the best practice would be to create a repository for you entity and make you the query there:
namespace App\Repository;
use App\Entity\Questions;
use Doctrine\ORM\EntityRepository;
class QuestionsRepository extends EntityRepository
{
public function findUserQuestions($user, $u)
{
$queryBuilder = $this->createQueryBuilder('c')
->where('c.questToID = :questToID')
->andWhere('c.questFrom = :questFrom')
->setParameters(['questToID'=>$u,'questFrom'=>$user]);
return $queryBuilder->getQuery()->getResult();
}
and in you controller you can get the result:
$userQuestions = $this->getDoctrine()->getRepository(Questions::class)->findUserQuestions($this->getUser(), $u);
count($userQuestions);
i hope this will be useful in your case and explain you a bit how its done in symfony
One simple way of doing that would be something like this (with the assumption that you have a "Question" entity - and that the fields defined there are matching with the column names from your raw query).
$em = $this->getDoctrine()->getManager();
$userQuestions = $em->getRepository(Question:class)->findAll(['questToID' => $_POST['u'], 'questFrom ' => $this->getUser()->getId()]);
$total = count($userQuestion);
Or if you prefer to have just the count from the query instead of fetching all the matching objects and counting them, you can write the query builder part something like this (in this format this is meant to be written in your controller as you have with your raw query - in general the "correct" place would be QuestionRepository.php class from where this would be just called in controller):
$em = $this->getDoctrine()->getManager();
$qb = $em->createQueryBuilder('q');
$qb->select('count(q)');
$qb->andWhere('q.questToID = :questToID');
$qb->andWhere('q.questFrom = :questFrom');
$qb->setParameter('questToID', $_POST['u']);
$qb->setParameter('questFrom ', $this->getUser()->getId());
$total = $qb->getQuery()->getSingleScalarResult();
Is there a way for the model to compute a field before save?
I have a schema something like this:
item_id
item_qty
item_price
linetotal
I'm trying to avoid this kind of code in my controller:
$model->linetotal = $f3->get('POST.item_qty') * $f3->get('POST.item_price');
$model->save();
Instead, I want to setup my model to compute the linetotal before save.
Perhaps something like the beforeSave callback in cakephp, or even better maybe I missed that I can set it up just like a Mapper virtual field on f3, except its a real field...
What you're asking for has just been released in 3.2.2. DB mappers now come with the following hooks: beforeinsert, afterinsert, beforeupdate, afterupdate, beforeerase, aftererase and onload.
So you could implement the linetotal calculation like this:
class myModel extends \DB\SQL\Mapper {
static function _beforeupdate($self,$pkeys) {
$self->linetotal = $self->item_qty * $self->item_price;
}
function __construct() {
$f3=\Base::instance();
parent::construct($f3->get('DB'),'mytable');
$this->beforeupdate(array(__CLASS__,'_beforeupdate'));
}
}
But since the calculation is also relevant for INSERT statements, you'll also need to hook the beforeinsert event. You could use the same function to hook both events :
class myModel extends \DB\SQL\Mapper {
static function _linetotal($self,$pkeys) {
$self->linetotal = $self->item_qty * $self->item_price;
}
function __construct() {
$f3=\Base::instance();
parent::construct($f3->get('DB'),'mytable');
$this->beforeinsert(array(__CLASS__,'_linetotal'));
$this->beforeupdate(array(__CLASS__,'_linetotal'));
}
}
NB: an alternative way to implement the linetotal calculation would be to simply override the mapper set() method:
class myModel extends \DB\SQL\Mapper {
function set($key,$val) {
parent::set($key,$val);
if ($key=='item_qty' || $key=='item_price')
$this->linetotal = $this->item_qty * $this->item_price;
}
}
EDIT:
I forgot a third alternative, which actually looks more appropriate in your case: virtual fields. In this case, the calculation is left to the DB engine. E.g:
class myModel extends \DB\SQL\Mapper {
function __construct(){
$f3=\Base::instance();
parent::construct($f3->get('DB'),'mytable');
$this->linetotal = 'item_qty * item_price';
}
}
PS: don't forget to remove the linetotal field from your database.
I made a library class that I am using for some common functions not provided by Laravel. It's been loaded into /config/app.php under the 'aliases' array, so that shouldn't be the problem.
When I call a method from my class ("InfoParse"), my conroller returns a blank page. I think this has to do with the fact that I'm calling a method from the library which uses Eloquent to interface with the database. I tried adding
use Illuminate\Database\Eloquent\Model;
to the top of the file, but that didn't help either.
Is there a specific way I should be setting up my class file so I can use either the DB:: class or Eloquent class?
Below is the function in question:
/**
* Check to see if this student is already recorded in our student table.
* If not, add the entry, then return true.
* #param int $cwid
* #return boolean
*/
public static function checkStudentTableRecords($cwid)
{
if(Student::where('cwid', '=', $cwid)->count() != 0)
{
return TRUE;
}
else
{ ##insert the student into our student table
$studentInfo = self::queryInfoFromCWID($cwid);
$studentEntry = new Student;
$studentEntry->cwid = $cwid;
$studentEntry->fName = $studentInfo['fName'];
$studentEntry->lName = $studentInfo['lName'];
$studentEntry->email = $studentInfo['email'];
$studentEntry->save();
return TRUE;
}
}
(note: the self::queryInfoFromCWID() function is calling a function defined earlier in the class)
After some investigation, it turns out I need to format my Eloquent Model calls like this:
if(\Student::where('cwid', '=', $cwid)->count() != 0)
...
$studentEntry = new \Student;
The backslash is necessary to avoid namespace collision within the Laravel4 application.
Imagine i have 2 classes (i've simplified the logic here):
class Table {
public function addRow (Row $row){
$this->row = $row;
}
// lots of code
}
class Row {
// lots of code
}
And i want to extend the table class to do something similar so i create 2 new classes:
class SpecialTable extends Table{
public function addRow (SpecialRow $row){
parent::addRow($row);
}
// lots of code
}
class SpecialRow extends Row{
// lots of code
}
When i try to add a SpecialRow to a SpecialTable object i get a warning similar to:
PHP Strict standards: Declaration of SpecialTable::addRow() should be compatible with that of Table::addRow() in /SpecialTable.php on line XX
Can someone help me here? Is this bad practice and i should code it differently? Or is it just a warning that i should ignore?
Many thanks for any suggestions / guidance.
You should probably use a name like SpecialTable::addSpecialRow() if you're changing the argument type - otherwise SpecialTable doesn't actually extend Table, it overloads it (which isn't supported in PHP).
Based on your simplified example, it should be public function addRow(Row $row) since you just call the parent. Depending on what you're actually doing to $row in that function, you could type-hint Row and check whether it's a SpecialRow in code, or just use Row if you don't need its Specialness.
Make sure SpecialRow extends from Row:
class SpecialRow extends Row {
// lots of code
}
And that SpecialTable extends from Table:
class SpecialTable extends Table {
// lots of code
}
If Row and SpecialRow aren't directly related (where SpecialRow is a specialized Row and therefore should extend Row), you could use an interface. PHP doesn't support method overloading without some crazy ugliness, so an alternative might be something like this:
Interface:
interface IRow
{
// interface body
}
Row classes:
class Row implements IRow
{
}
class SpecialRow implements IRow
{
}
Table classes:
class Table
{
public function addRow(IRow $row)
{
$this->row = $row;
}
}
class SpecialTable
{
public function addRow(IRow $row)
{
$this->row = $row;
}
}
Implementation:
$t = new Table();
$st = new SpecialTable();
$r = new Row();
$sr = new SpecialRow();
$t->addRow($r);
$st->addRow($sr);
http://ideone.com/TsVw5
Since it doesn't follow PHP's standards I'd call it bad practice. The point is that a derivative class should always work like its ancestor. So classes expecting a Row argument can handle a SpecialRow the same way. You're extending the class, not overloading it. I'd suggest adding another method for this purpose.