How to write count(*) SQL in DQL, Doctrine ORM - php

How to get count of rows in database by id?
SELECT count(*) FROM members;
Without performance issues. What are ways to write this query using entityManager?
I am using php version 5.6 and symfony 3

You have to use your EntityRepository
Add a function in it and write something like this:
$queryBuilder = $this->_em->createQueryBuilder()
->select('COUNT(e)')
->from('AppBundle:Entity', 'e');
return $queryBuilder->getQuery()->getSingleScalarResult();

Edit: Just saw Gregoire's answer. That will work. However, if you already have the Entity which has the relation, and it's initialized, the below would allow you to get this info without an additional query to the DB.
You could use the association and get it from the Collection (see Working with Associations in the docs
class SomeEntity
{
/**
* #var Collection|Member[]
*/
protected $members;
// other properties
// PHP >= 7 -> public function countMembers(): int
public function countMembers()
{
return $this->getMembers()->count();
}
// PHP >= 7 -> public function getMembers(): Collection
public function getMembers()
{
return $this->members;
}
// other getters/setters
}

Related

how to use CONVERT() function MySQL in Doctrine

I have a field in an entity in string type, I need order result in my dql to value integer
I have a field in an entity in string type, I need the result sorted by that field but converted in an integer.
Some like this (MySQL Query):
SELECT * FROM table1 ORDER BY CONVERT(code, UNSIGNED);
How do I create this query in doctrine?
UPDATE
I managed to do it with the cast function thanks to this post:
CASTING attributes for Ordering on a Doctrine2 DQL Query
I have created my own function to implement this feature.
Official doc in doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#adding-your-own-functions-to-the-dql-language
AFAIK You can't do it directly . Doctrine don't support native mysql functions (convert ,day,month etc).
Idea od doctrine is to be able to talk with many different databases - and it's why there isn't any native functions.
but
you can do it on your own.
Some years ago i needed data functions (day/month etc ) in doctrine so i manage to add it do doctrine .
look here :
https://github.com/poznet/SF2Core/blob/master/src/Poznet/CoreBundle/Dql/Year.php
or
https://github.com/beberlei/DoctrineExtensions
seen solutions for conver too , but never tested it , look here
https://gist.github.com/liverbool/6345800
<?php
class ConvertUsing extends FunctionNode
{
public $field;
public $using;
public $charset;
/**
* #override
*/
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return sprintf('CONVERT(%s USING %s)',
$sqlWalker->walkArithmeticPrimary($this->field),
//$sqlWalker->walkSimpleArithmeticExpression($this->using), // or remove USING and uncomment this
$sqlWalker->walkSimpleArithmeticExpression($this->charset)
);
}
/**
* #override
*/
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->ArithmeticPrimary();
// adopt use bypass validate variable of parse by using AliasResultVariable ...!!
$this->using = $parser->AliasResultVariable();
$this->charset = $parser->AliasResultVariable();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
If you only want to sort your query you can sort the code even if it's a string like this.
SELECT * FROM table1 ORDER BY code ASC; <- for ascending
SELECT * FROM table1 ORDER BY code DESC; <- for descending
If you want to convert it to integer try this
SELECT * FROM table1 ORDER BY (SELECT CONVERT(int, code));
Just make sure your codes are all numbers so you will not get an error.
I hope it helps.

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

Laravel changing sorting of collection when sending mail (between constructor and build function call)

this is laravel 5.3
when I preview the email using this:
$wantsheet_products = WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get();
View::make('email.wantsheet.email_wantsheet_to_supplier', ['wantsheet_products' => $wantsheet_products]);
the sorting is correct. that is, sorting is ['a','b','c'] the way i want it.
EDIT see note at the bottom
now when actually sending out the mails (i queue them), the sorting changed and is unsorted again, magic?! the change happens between the constructor and the build function
class WantsheetToSuppliersMail extends Mailable
{
public $wantsheet_products;
public $to_email;
/** #var WantsheetContact $wantsheetcontact*/
public $wantsheetcontact;
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($wantsheet_products)
{
//$wantsheet_products is a standard eloquent model collection, e.g. i get it like this: WantsheetProduct::orderByRaw(self::WANTSHEET_PRODUCT_ORDER_SQL)->get()
$this->wantsheet_products = $wantsheet_products; //is ['a','b','c']
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
// $this->wantsheet_products is ['b','a','c'];
$subject = 'abc';
return $this->from('me#myapp.com')->view('email.wantsheet.email_wantsheet_to_supplier', [])->subject($subject);
}
}
EDIT contd.
Now when i do
WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get()->toArray();
it doesn't break the sorting any longer (so it works). But that is stupid, isn't it?
When your mail object is queued for delivery, it takes your Collection of Model instances, gets their ids, and stores the list of ids on the queued job. When the queued job is then processed, it takes those Model ids, and retrieves the data from the database.
The problem, however, is that the query being run to rebuild the collection doesn't care about the order of the ids. It just runs a whereIn() statement with the list of ids.
Everything worked when you converted your Collection toArray() because it also converted all your Models to arrays. So, it was no longer a Collection of Models, it was an array of arrays. There is no special serialization that takes place there, so the data went across exactly as you sent it.
The easiest way to get your order back is probably to override the restoreCollection method, so you can add in your order by clause to the restoration query. Add this method to your WantsheetToSuppliersMail class:
protected function restoreCollection($value)
{
if (! $value->class || count($value->id) === 0) {
return new EloquentCollection;
}
$model = new $value->class;
return $model->newQuery()->useWritePdo()
->whereIn($model->getKeyName(), $value->id)
->orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)
->get();
}
This is the same as the current function, just that your custom order by has been applied to the query.
it is a known bug of laravel 5.3
basically reretrieve the objects in the build function e.g.
public function build()
{
$this->wantsheet_products = WantsheetProduct::orderByRaw(EmailService::WANTSHEET_PRODUCT_ORDER_SQL)->get();
$subject = 'abc';
return $this->from('me#myapp.com')->view('email.wantsheet.email_wantsheet_to_supplier', [])->subject($subject);
}

Laravel 4 - Using Eloquent Models in a custom library class

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.

Creating a Pagination class in PHP

I want to build a paginator class, but i'm not sure what the best method is. I see many different codes on the internet that confuses me.
My data is stored in a MySQL database. Do i have to give a query to my pagniator class so that it can retrieve the data out of MySQL for me (and of course it will automatically do some pagination calculations after that)? Or do i get all the data from a model first, then supply the returned array from that model to the paginator class?
In either way i'd probably have to do another query to get the "total" amount of records and pass that result to the pagninator class as a separate param.
Once i know how to get started then i know how to pick up the rest. I'm just not sure how to pass in the data to the paginator class and what kind of data.
Any idea how to build a good paginator class?
A well designed OO Paginator class should be independent of your data and your database.
It should take parameters like count, page, and per_page, and should return objects that can be used for Dependency Injection into a Model for generating queries with the appropriate LIMIT, or into a PaginationHelper class for rendering the appropriate HTML.
Example of what might be a good Paginator interface (given 10 minutes of thought in this):
/**
* Your pagination master class
*/
interface iPaginator {
public function __construct($total_count, $count_per_page, $current_page=1);
public function getPaginationLimiter();
public function getPaginationHelper();
// These return iPaginatorPage objects
public function getCurrentPage();
public function getNextPage();
public function getPreviousPage();
public function getTotalCount();
public function getCountPerPage();
public function getPageCount(); // Calculated
}
/**
* Page representation
*/
interface iPaginatorPage {
public function __construct($page_number, $start, $end);
public function getNumber();
public function getStart();
public function getEnd();
public function getCount(); // Calculated
}
/**
* Helper for rendering the UI
*/
interface iPaginationHelper {
public function __construct(iPaginator $paginator);
public function render();
}
Example of how you could integrate into your model, by extending your base model, and then having your application models extend PaginatorModel instead of Model:
class PaginationModel extends Model {
public function query($sql, iPaginatorPage $page = null) {
if (!empty($page)) {
$start = $page->getStart();
$length = $page->getCount();
$sql .= " LIMIT ({$start}, {$length})";
return parent::query($sql);
}
}
}
Check the pager implementation from Pear. It's a very nice class, simple to use and with a lots of cool features.
create class, and have function like this:
function resultset($required_filters, $table, $pagenum, $records){
$getCount='SELECT count(*) from table';
$sql="SELECT ".$required fields." FROM ".$table." LIMIT ".(($pagenum-1) * $records).",".$records;
$sqlres=...
return array($getCount, $sqlres); }
call this function as list($rescount, $arrData)=resultset(all
parameters);
and for pagination use some for loop to display page numbers...for total $rescount pages,
using get you can show current pagenum highlighted

Categories