Skip to content

Querying Data Reading Data via API

Adarsh Kumar Maurya edited this page Nov 29, 2018 · 1 revision

Querying Data - Reading Data via API

We’re at the point now where we know that we’ve got some routes built, we have some controllers that can start to do some work, even though the methods are still empty at the moment. So what we want to do, is we actually want to turn our eyes to what need to fill these methods inside these controllers to make the application function correctly.

So we’re going to kind of call this section like what do we need to do to like query the data. So this isn’t necessarily putting data in, it’s just simply how do we read the data and make sure that the end points are doing the job correctly.

When we go in and do that, we can start with our ProductController, and remember we have our index method, which is meant to show the list of all of the products that are in the database. So we can actually copy the result of this previous method, this closure that we had, and bring it over into our ProductController, into the index method, and what we want to do now though is to, because the namespace was originally just App and then product, but this controller is actually in this App HTTP controllers namespace, so we need to add an using statement to the top of this controller, to tell it that we want to use App Product. So now when we reference the product, and use this all method, we know which model we’re actually using directly. Otherwise it would have tried to think that there’s like an App namespace inside of controllers, and then a product inside there and it would not find it all together.So namespacing becomes really important. Make sure that you’re always using the right objects, make sure they’re being included correctly. So in this particular case, all we needed to do was bring over our index for our index method, the GET all for our products, cause that should make that work. Great.

<?php

namespace App\Http\Controllers;
**use App\Product;**
use Illuminate\Http\Request;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
      **  return Product::all();**
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    // public function create()
    // {
    //     //
    // }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    // public function show($id)
    // {
    //     //
    // }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    // public function edit($id)
    // {
    //     //
    // }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    // public function destroy($id)
    // {
    //     //
    // }
}

Now if when we try to bring that same logic over to our ProductDescriptions controller, we don’t actually want to use our product model, what we actually want is descriptions that belong to a given product, right? And so this one, what we want to do is we want to change it just a little bit, so instead of using our App product model we want to use our App description model. We also want to make sure that this method will accept the parameter that is the product ID that’s passed into the route itself, so when we come back and look at this, this method here is basically saying there’s going to be a products end point, there’s going to be a resource ID and then there’s going to be a descriptions end point.

Route::group(['prefix' => 'api'],function(){
  // Route::get('products',[
  //   'as' => 'products',
  //   function(){
  //     return App\Product::all();
  //   }
  // ]);
    Route::resource('products','ProductController',['only' =>['index', 'store', 'update']]);
    Route::resource('products.descriptions','ProductDescriptionController',['only' =>['index', 'store']]);
});

That product ID, we need to make sure gets passed into the method so we actually know what to do with it. Laravel will do all that association for you, so all we’ve got to do is to say that this method expects there to be a product ID passed into it, and then when we actually want to get our descriptions, we have to do a little bit of work to make sure we are getting only the descriptions that belong to the products in question, right?

So when we do that, what we need to do is we basically build a scope. So scopes are a bit of infrastructure that are part of the Laravel and Eloquent ecosystem and we basically go to our models and we say, in this particular case, our description, we can define what this scope is.

So scopes are, think of them as like query modifiers.

So previously we might say get * from descriptions where, or just get * from descriptions, but what we actually want to do is we want to say get * from descriptions where productId equals the given productId.

So for this example, we can actually build a scope here, called scopeOfProduct and this method, the first parameter is automatically provided for you, it’s the actual query, but then we actually want to have the productId passed into that. And these methods always return the query itself.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Description extends Model
{
    public function product(){
      $this->belongsTo(Product::class);
    }

    public function scopeOfProduct($query, $productId){
      return $query;
    }
}

So I’ll back up and explain why this looks the way that it does right now. So whenever you want the scope to be defined, you just use a prefix on the method name called scope. And then anything else after it is going to become the camel case method that you can use to filter your query.

So you’re not going to be calling scopeOfProduct, you’re going to be calling OfProduct. And then I’m going to pass in the productId and then there’s some internal magic plumbing that’s going to actually take the $query that was already being built, pass that in as the first parameter, but then pass in what I provide to that method as being the next parameter. So if it needs to require three parameters, there would be four here actually. There would be query, and then there would be the three that you need to provide.

So if we wanted this one to actually work correctly, we would do Description::ofProduct(), pass in the $productId that’s been provided into the controller method and then to close and finish the query, we just say get(). So what this is going to do is it’s going to attempt to, well the intention of this is to take the productId that’s given, try and filter the query results, and then send them all back.

class ProductDescriptionController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index($productId)
    {
        //return Description::all();
        return Description::ofProduct($productId)->get();
    }
...

The problem right now though is that we haven’t completely finished the scope on the Description model, so right now it’s just going to return the query, which means it hasn’t actually done anything to the query.

So here what we can do is we can say, we want a query where product_id equals $productId.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Description extends Model
{
    public function product(){
      $this->belongsTo(Product::class);
    }

    public function scopeOfProduct($query, $productId){
      **return $query->where('product_id',$productId);**
    }
}

So our column in the database for descriptions is for product_id, there’s an ID, and then we’re basically just passing in the $productId that was given to us.

Why don’t we give this a shot? Let’s maybe test this. Can we test this here. So what we want to do is quickly add a seeder back to our so we can seed a few product descriptions.

So let’s actually seed the database with a few records here, so just like our ProductTableSeeder

Adarsh:product-service adarshmaurya$ php artisan make:seeder DescriptionTableSeeder
Seeder created successfully.
<?php

use Illuminate\Database\Seeder;

class DescriptionTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $now = date('Y-m-d H:i:s', strtotime('now'));
        
        for($i=0; $i < 5; $i++){
          DB::table('descriptions')->insert([
            'product_id'=>1
            'body' => uniqid(),
            'created_at' => $now,
            'updated_at' => $now,
          ]);
        }
    }
}

So I’m just going to manually insert a few records for. So here we want to do table descriptions. And we’ll do body. It’s going to be just some unique values. And then created at and then here for product ID. Let’s just set that equal to one because we know that there are, there is a product that is one in there. And then, let’s add a quick little four loop. So what we’ll do is we’ll just make sure that there are five descriptions that are mapped to product one.

And then we, now that we’ve created our our description table seeder, we can go back to the terminal and we can say php artisan db seed: --class=DescriptionTableSeeder

Adarsh:product-service adarshmaurya$ php artisan db:seed --class=DescriptionTableSeeder
Database seeding completed successfully.

So now we can go see that we have our descriptions in the database. We have our five.

So in order to test(using postman), what we want to do is just make sure that when we go to, when we do a GET for API products and then ID one, so product resource one and then slash descriptions.

get http://192.168.0.6:8000/api/products/1/descriptions
[
    {
        "id": 1,
        "product_id": 1,
        "body": "5bff2e4fad8da",
        "created_at": "2018-11-29 00:09:51",
        "updated_at": "2018-11-29 00:09:51"
    },
    {
        "id": 2,
        "product_id": 1,
        "body": "5bff2e4faf857",
        "created_at": "2018-11-29 00:09:51",
        "updated_at": "2018-11-29 00:09:51"
    },
    {
        "id": 3,
        "product_id": 1,
        "body": "5bff2e4fafbbb",
        "created_at": "2018-11-29 00:09:51",
        "updated_at": "2018-11-29 00:09:51"
    },
    {
        "id": 4,
        "product_id": 1,
        "body": "5bff2e4fafe94",
        "created_at": "2018-11-29 00:09:51",
        "updated_at": "2018-11-29 00:09:51"
    },
    {
        "id": 5,
        "product_id": 1,
        "body": "5bff2e4fb04d1",
        "created_at": "2018-11-29 00:09:51",
        "updated_at": "2018-11-29 00:09:51"
    }
]

So we’re getting all our descriptions. So those are our five. And then so now if you wanted to kind of test it out, you could change our resource from being product one to product two, which doesn’t have any of them and it’s just an empty collection.

get http://192.168.0.6:8000/api/products/2/descriptions
[]

The scopes are helpful for keeping the models in control of how they know how they know how they interact with other models or maybe how you want to interpret some data that’s built into the models, but we use the scopes to help us kind of not make our controllers get really bloated with additional logic or anything.

Something else that I want to bring up right at the moment here is that this all method, we kind of talked about that before, the all method is a bit unsafe, it’s kind of like select all and you could get into trouble if there are a bunch of other methods that are attached to it.

so what I like to do is there’s another method that’s attached to these model objects called paginate, and what it does is it builds in some extra infrastructure that helps you be able to control how you want to paginate your models, so if you instead of saying just give me all of them, it’s maybe like give me 15, starting with this page, and it actually provides some extra data that tells you how to access more and more pages, so next or previous.

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //return Product::all();
       ** return Product::paginate();**
    }
class ProductDescriptionController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index($productId)
    {
        //return Description::all();
        //return Description::ofProduct($productId)->get();
        **return Description::ofProduct($productId)->paginate();**
    }

So instead of there being all, we can just do paginate. And we’ll do this to both of our controller methods for index. So here instead of the GET we just do paginate. So let’s test that. So maybe go back and do your products one descriptions.

get http://192.168.0.6:8000/api/products/1/descriptions
{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "product_id": 1,
            "body": "5bff2e4fad8da",
            "created_at": "2018-11-29 00:09:51",
            "updated_at": "2018-11-29 00:09:51"
        },
        {
            "id": 2,
            "product_id": 1,
            "body": "5bff2e4faf857",
            "created_at": "2018-11-29 00:09:51",
            "updated_at": "2018-11-29 00:09:51"
        },
        {
            "id": 3,
            "product_id": 1,
            "body": "5bff2e4fafbbb",
            "created_at": "2018-11-29 00:09:51",
            "updated_at": "2018-11-29 00:09:51"
        },
        {
            "id": 4,
            "product_id": 1,
            "body": "5bff2e4fafe94",
            "created_at": "2018-11-29 00:09:51",
            "updated_at": "2018-11-29 00:09:51"
        },
        {
            "id": 5,
            "product_id": 1,
            "body": "5bff2e4fb04d1",
            "created_at": "2018-11-29 00:09:51",
            "updated_at": "2018-11-29 00:09:51"
        }
    ],
    "first_page_url": "http://192.168.0.6:8000/api/products/1/descriptions?page=1",
    "from": 1,
    "last_page": 1,
    "last_page_url": "http://192.168.0.6:8000/api/products/1/descriptions?page=1",
    "next_page_url": null,
    "path": "http://192.168.0.6:8000/api/products/1/descriptions",
    "per_page": 15,
    "prev_page_url": null,
    "to": 5,
    "total": 5
}

So now you can see that the data that came back out includes a little bit of extra metadata that tells you the total that are there, so there’s five, how many are per page, 15, and it even gives you, it attempts to give you the next and previous pages but there’s no more pages, so it doesn’t really work very well for that. So now if you go back and eliminate and just do, let’s get our collection of all the products. You can see the same sort of thing here. So there’s a total of six. These are all of our repetitive ones.