Paginator Symfony advice for making query - php

I am trying to make a paginator in Symfony and this is how my code looks like:
Controller class:
class MovieDisplayController extends Controller
{
public function showAction()
{
//Records:
//$movies = $this->getDoctrine()->getEntityManager()->getRepository('AppBundle:Movie')->FindAll();
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'SELECT m
FROM AppBundle:Movie m'
)->setMaxResults(5)->setFirstResult(0);
$movies = $query->getResult();
//$resultAmount = $query->getResult()-count();
//Pagination:
$filterVariables = "";
$currentPage = (isset($_GET['page'])) ? $_GET['page'] : 1;
$totalPages = /*Example: */20; //something like: $this->count($movies) / $recordsPerPage
return $this->render('movies/index.html.twig', array(
'movies' => $movies,
'filtervariables' => $filterVariables,
'page' => $currentPage,
'totalPages' => $totalPages
));
}
}
This code works perfectly fine, however I want to make my paginator work and I have to pass some GET variables in the LIMIT part of a query (at least that is what I found out after doing some searching).
First of all, let me ask if any of you would know, the use of the GET variables in the "pagination" part of the class, would that be the proper way in Symfony to call those? I have seen something like: $foo = $request->query->get('page'); but that does not seem to return anything (or at least not the same as the $_GET['page'] variable), or am I trying to do something that is not possible (anymore)?
(Also I do not wish to make use of routing for this)
Secondly I would like to know if it is possible (or how) to count the amount of results I would get by using $movies = $query->getResult().
If anyone has any tips or advice for me to optimize my code and make it working, I would gladly want to know.
Edit: My pagination works fine now, if there should be any tweaks to make it even work more perfectly, feel free to add a comment or an answer if it's an important change. (Code used)
Thanks in advance.

GET and SET variables within symfony
There are several possibilities how to pass variables to controller. The simplest will be to use the routing.
/**
* #Route("something/{page}/{limit}", defaults={"page" = 1, "limit" = 10}, name="show_action")
*/
public function showAction($page, $limit)
{
}
Then you will be able to use $page and $limit directly inside your controller.
If you don't want to use routing you may also use session instead:
public function showAction(Request $request)
{
$this->get('session')->get("page");
$this->get('session')->get("limit");
}
Counting records
You may count your sql records using DQL:
$query = $this->createQueryBuilder()
->from('Movie', 'f')
->getQuery();
$total = $query->select('COUNT(f)')
->getQuery()
->getSingleScalarResult();

Related

Is it possible to add custom functions on Laravel Models?

I have an Orders table that has relations to a Movements table, and im constantly doing things like this to calculate several common values for each order:
$warehouse = 7;
$order = Order::find(16111);
$entries = Movement::selectRaw("SUM(gross) AS total_gross")
->selectRaw("SUM(net) AS total_net")
->selectRaw("SUM(qty) AS total_qty")
->where('order_id', $order->id)
->where('to_id', $warehouse)
->first();
$exits = Movement::selectRaw("SUM(gross) AS total_gross")
->selectRaw("SUM(net) AS total_net")
->selectRaw("SUM(qty) AS total_qty")
->where('order_id', $order->id)
->where('from_id', $warehouse)
->first();
is it possible to create a custom function to just query the DB doing something like this:
$warehouse = 7;
$entries = Order::find(16111)->entries($warehouse);
$exits = Order::find(16111)->exits($warehouse);
If so how can it be done?
Thanks for your help...
Absolutely. What you are looking for is called local Query Scopes; it allows you to avoid repeating complexe queries in your code.
Local scopes allow you to define common sets of query constraints that you may easily re-use throughout your application.
Write your local query scope in your model and you'll never have to repeat this code again (DRY principle).
Here's an example to give you an idea, you'll need to tweak it to your needs.
In your Order model:
public function scopeEntries($query)
{
$warehouse = $this->warehouse; // Take advantage of Eloquent wherever you can
return $query->movements()->selectRaw("SUM(gross) AS total_gross")
->selectRaw("SUM(net) AS total_net")
->selectRaw("SUM(qty) AS total_qty")
->where('to_id', $warehouse->id);
}
public function scopeExits($query)
{
$warehouse = $this->warehouse; // Take advantage of Eloquent wherever you can
return $query->movements()->selectRaw("SUM(gross) AS total_gross")
->selectRaw("SUM(net) AS total_net")
->selectRaw("SUM(qty) AS total_qty")
->where('from_id', $warehouse->id)
->where('to_id', $warehouse->id);
}
Now in your code, you will be able to simply call $order->entries()->first() to retrieve the first entry but you can also call $order->exits()->get() to retrieve all exits.

Laravel Eloquent pagination control page number with route

Articles::paginate(10)
This code will return the 1st 10 articles, what if I want to return the next 10 articles with route? For example the url mypage.com/articles/2 will return the 2nd 10 articles from database.
This is so far what I have:
Route:
Route::get('articles/{page_number}', 'Controller#getArticles')
Controller:
public function getArticles($page_num)
{
$perPage = 10;
Articles::getPaginator()->setCurrentPage($page_num);
$articles = Articles::paginate($perPage);
return $articles;
}
Can I have something like Articles::pageNumber($page_number)->paginate($perPage);?
Laravel paginator automatically checks for the the value of page in query string and uses it to paginate the results. The result also automatically generates the next and previous links to help you add them directly. You don't need to change anything to make it work.
In your case you can use $articles->links() in your view to generate the pagination navigation buttons. But if you want to manually set the page then you can do this.
$articles = Articles::paginate(5, ['*'], 'page', $pageNumber);
The default paginate method takes the following parameters.
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);
The default convention is
mypage.com/articles?page=2
mypage.com/articles?page=3
Also if you use $articles->links() to generate the navigation button, you can also customize the css.
Check out https://laravel.com/docs/5.4/pagination for more info
$results= DB::table('subscribers')->select('id', 'name', 'email')->paginate(20);
$results->count();
$results->currentPage();
$results->firstItem();
$results->hasMorePages();
$results->lastItem();
$results->lastPage(); (Not available when using simplePaginate)
$results->nextPageUrl();
$results->perPage();
$results->previousPageUrl();
$results->total(); (Not available when using simplePaginate)
$results->url($page);

Laravel: efficient way to count related models with where clause

I have two models User and Posts
User: id,name
Post: id,title,post,user_id
I want to check if some user has posts with given title.
$user = User::find($userId);
$posts = $user->posts;
$postsWithGivenTitle = $posts->where('title','=',$title);
$postCount = $postsWithGivenTitle->count();
I suppose this above should work but I need to go above this and do it efficiently. So I got this and it's working but still not sure is it the right way to do it.
$user = User::find($userId)->withCount(['posts' => function ($q) use ($title) {
$q->where('title','=',$title);
}])->first();
and then to check the count:
if ($user->posts_count > 0) {
//do something
}
What's confusing me, and looks ugly, is using the methods find() and first() in the same query. So hopefully I'm missing something simple here and overthinking it.
Thanks
As you can see in the docs: https://laravel.com/docs/5.3/eloquent-relationships
You can acheive what you want like this:
$user = User::withCount(['posts' => function($q){....}])->find($id)

Passing a parameter to a custom find method in cakephp 3.x

I want to build a custom find function that retrieves bands for a given genre, i have tried this but the function can't access to the parameter $genre:
public function findGenre(Query $query, array $options)
{
$genre = $options['genre'];
$bands = $this->find()->contain([
'Genres' => function($q){
return $q->where(['Genres.id' => $genre]);
}
]);
return $bands;
}
I can access the $genre outside the contain() method, but not inside it.
My question is, how can i pass the $genre var to the function($q) inside the contain method.
I found where the problem is, i had to use the keyword use after the function($q), so that part of the code will look like this
$bands = $this->Bands->find()->contain('Genres', function($q) use ($genre){
return $q->where(['Genres.name'=>$genre]);
});
Also,the contain() method returns all the data even if the bands don't belong to a genre, but when i replaced it with matching() it worked just fine.
I hope this will help anyone who is having a similar problem in the future.
I was facing same issue but now it's resolved. I will explain you step by step:
My tables are:
articles: id,name,status,created
tags:id,name,status,created
articles_tags: id,article_id, tag_id
my query is this:
I want to pass my $tag_data['slug'] in matching variable but
directly this variable is not working in query. So I put in simple
$uses variable and now it's working properly.
$uses = $tag_data['slug'];
$contain_article = ['Tags'];
$query = $this->Articles->find('All')
->where(['Articles.status' => '1'])
->contain($contain_article)
->matching('Tags', function ($q) use ($uses) {
return $q->where(['Tags.slug' => $uses]);
});
Please try this :-)

inconsistent amount of data when I use pagination in CodeIgniter

I want to perform a large of data using paging library in CodeIgniter. I have been implementing it and it works. But, I have a problem - amount of data per page is not consistent.
This is the simple code how Imade it...
Controller
class Buku_con extends Controller {
public function Buku_con() {
parent::__construct();
$this->load->model('buku_model');
$this->load->library('pagination'); //call pagination library
}
function getBuku() {
//count the total rows of tb_book
$this->db->select('*');
$this->db->from('tb_book');
$getData = $this->db->get('');
$a = $getData->num_rows();
$config['base_url'] = base_url().'index.php/Buku_con/getBuku/'; //set the base url for pagination
$config['total_rows'] = $a; //total rows
$config['per_page'] = '10'; //the number of per page for pagination
$config['uri_segment'] = 3; //see from base_url. 3 for this case
$config['full_tag_open'] = '<p>';
$config['full_tag_close'] = '</p>';
$this->pagination->initialize($config); //initialize pagination
$data['detail'] = $this->buku_model->getBuku($config['per_page'],$this->uri->segment(3));
$this->load->view('buku_view', $data);
}
}
Model
class Buku_model extends Model {
function Buku_model() {
parent::Model();
}
function getBuku($perPage,$uri) { //to get all data in tb_book
$title=$this->session->userdata('title');
$this->db->select('*');
$this->db->from('tb_book');
$this->db->where('title','$title');
$this->db->order_by('id','DESC');
$getData = $this->db->get('', $perPage, $uri);
if($getData->num_rows() > 0)
return $getData->result_array();
else
return null;
}
}
View
if(count($detail) > 0) {
//... html for table .....
foreach($detail as $rows) {
echo
.... $rows['id'] .....
.... $rows['title'] .....
.... $rows['author'] .....
....
}
echo $this->pagination->create_links(); ....
The paging works well, but amount of data per page is not consistent like I define in controller. I think the problem is caused by the query that I use in model - Session of title.
I want the data perform 10 data per page, but in page 1 just 5 data, page 2 just 4 data - not consistent. Maybe the problem is also in the view. What should I do? Thank you very much.
you have not properly calculated the offset. You may try this:
$offset = ( $this->uri->segment(3) - 1 ) * $config['per_page'];
$data['detail'] = $this->buku_model->getBuku($config['per_page'], $offset);
you might wanna look at this for proper pagination using codeigniter Pagination Library
https://stackoverflow.com/a/10123424/876117
You don't get all the data for each request. You need to fetch only data as specified the offset in the link generated by the pagination library.
Dude, there is a lot of queries on your Controller. And also on Model (of corse).
First: Try to consume almost only functions on your controller. Functions that you create on a model. Thats is the right MVC way.
And about your problem with pagination:
Try to use and abuse of GET variables. Kind, you can pass a url like that:
http://site.com/controller/function?p=2
See? "?p=2"? That way I can say to my controller that I want to see the second page os the list. You know, I can pass more GET variables, like:
http://site.com/controller/function?p=2&cat=romance&year=2010
And theres the way you will work these variables:
Lets take the second exemple ("?p=2&cat=romance&year=2010")
$page = $this->input->get('p'); // this is more secure too. avoid use $_GET or $_POST
$category = $this->input->get('cat');
$year = $this->input->get('year');
Now you can use some functions to get a list of books that matches to the values:
$data['books'] = $this->book->get_by_search($page, $category, $year);
PS: On this example, you got load a model 'book'that holds a function called 'get_by_search'. You have to do it to get the code working.
Tip: We never know how many variables a search can get. So you can create some model functions that receives those possible variables. Look:
//model Book
public function get_by_search($page = NULL, $cat = NULL, $year = NULL, $author = NULL)
{
$perpage = 10;
// the NULL is to prevent an error if the function is called without them.
if ( ! is_numeric($page) || $page < 0)
{
$qtd = $perpage;
$offset = 0;
}
else
{
$qtd = $page * perpage;
$offset = $perpage;
}
$this->db->where('category', $cat);
$this->db->where('year', $year);
return $this->db->get('books', $perpage, $offset);
// if you want to debug your queries, do the same above, but with no return
// and then use this:
echo this->db->last_query(); die; // stop here and see the query string.
}
well, I hope that it can help you. There is more to tell you but I think that its better you tell me more about your progress with the code. This answer is just a part of the solution. See? thats is not that simple, but I know how. Let me know here if you need more help, ok?
Um abraço!

Categories