Okay, so the last few lessons have got us up to the point where we’re able to send and receive data to the API, but there are some problems that need to be thought about:
- We’re exposing our database architecture – people can see orders have fields “order_ref”, “recipient_id”, etc.
- Our index functions are using “all()”, so they get all results from the database.
- We’re not validating our data before adding it to the database.
- We’re not authenticating users.
Lets star addressing these.
Transformers
Transformers are often used in APIs to obscure and abstract the database later from the responses provided to users. What this means is we “transform” what our database record field names are, and turn them into something else. Say in our database we were storing a field “recipient_name”. Instead of the API returning this to the user on a get request, we could use a transformer to return a different field name “name” for example. This obscures our database architecture so we’re not giving away our field names. Additionally, the abstraction here means that if we change our database architecture we’re not relying on API users to change their tools or utilities as well. We can change the database field names without worrying about what users are doing.
Variants Transformer
Once again, I’m going to start with the Variants as this is the smallest part of the API. All we do here is get variants, we don’t allow them to be added, updated or deleted. I’m going to start by looking at my project structure. At the moment we should have Http/Controllers/api and all of the controllers should be within this. It doesn’t really make sense to put transformers here, as they’re not controllers. Instead, I think we should make a new folder in the app directory, and lets version it too, incase different versions of the API use different transformers
mkdir app/Transformers mkdir app/Transformers/V1
Now lets make a new VariantsTransformer.php file in that directory:
touch app/Transformers/V1/VariantsTransformer.php
Open that file in Atom and lets make our transformer
<?php namespace App\Transformers\V1; // We need to reference the Variants Model use App\Variants; // Dingo includes Fractal to help with transformations use League\Fractal\TransformerAbstract; class VariantsTransformer extends TransformerAbstract { public function transform(Variants $variant) { // Specify what elements are going to be visible to the API return [ 'id' => (int) $variant->id, 'size' => $variant->size, 'brand' => $variant->brand, 'type' => $variant->type, 'color' => $variant->colour, 'design' => $variant->design, ]; } }
All we’re doing here is transforming our Database collection into an array and returning it. The left hand side of the array define the keys that will be used for the JSON response. The right hand side gets the variant fields from the database. What this empowers you to do is hide database fields – like created at – so there’s no risk they’ll be visible to the API. Only items in this array will be returned in API requests. The next key advantage is that the variant database field name doesn’t have to match the API field name. This means that if there’s a major database update that needs to take place, you can update the transformer and not have to ask customers to re-map everything in their APIs.
In the VariantsController, we need to change our functions to adopt the new transformer
use App\Transformers\V1\VariantsTransformer;
public function index() { // Return variants via the Variants Transformer. return $this->collection(Variants::all(), new VariantsTransformer); }
public function show($id) { return $this->item(Variants::find($id), new VariantsTransformer); }
I’ve added the show function here to return an individual variant instead of a collection. You’ll also need to add the route for it to work:
$api->get('/variants/{id}', 'App\Http\Controllers\api\VariantsController@show');
Now when you call the variants controller in postman, you should see array values coming through as keys in JSON, not database field names.
Following this theme we need to update the Items controller, as well as creating an ItemsTransformer, and the orders controller along with an OrdersTransformer.
ItemsTransformer:
<?php namespace App\Transformers\V1; // We need to reference the Items Model use App\Items; // Dingo includes Fractal to help with transformations use League\Fractal\TransformerAbstract; class ItemsTransformer extends TransformerAbstract { public function transform(Items $item) { // specify what elements are going to be visible to the API return [ 'id' => (int) $item->id, 'item_ref' => $item->item_ref, 'quantity' => (int) $item->quantity, 'variant_id' => (int) $item->variant_id, ]; } public function deform(Items $item) { // specify what elements are going to be visible to the API return [ 'id' => (int) $item->id, 'item_ref' => $item->item_ref, 'quantity' => (int) $item->quantity, 'variant_id' => (int) $item->variant_id, ]; } }
ItemsController:
// At the top of the file, include the items tranformer use App\Transformers\V1\ItemsTransformer; // Update the index function to use the ItemsTransformer public function index() { return $this->collection(Items::all(), new ItemsTransformer); } // Update the show function to use the items transformer public function show($id) { return $this->item(Items::find($id), new ItemsTransformer); }
OrdersTransformer:
<?php namespace App\Transformers\V1; // We need to reference the Orders Model use App\Orders; // Dingo includes Fractal to help with transformations use League\Fractal\TransformerAbstract; class OrdersTransformer extends TransformerAbstract { public function transform(Orders $order) { // specify what elements are going to be visible to the API return [ 'id' => (int) $order->id, 'order_ref' => $order->order_ref, 'recipient_id' => $order->recipient_id, 'shipping_method' => $order->shipping_method, ]; } public function deform(Orders $order) { // specify what elements are going to be visible to the API return [ 'id' => (int) $order->id, 'order_ref' => $order->order_ref, 'recipient_id' => $order->recipient_id, 'shipping_method' => $order->shipping_method, ]; } }
OrdersController:
// Include the orders transformer use App\Transformers\V1\OrdersTransformer; // change the index function to use the transformer public function index() { return $this->collection(Orders::all(), new OrdersTransformer); } // Change the show function to use the transformer public function show($id) { return $this->item(Orders::find($id), new OrdersTransformer); }
Save all of your work, git commit it if you’re being safe (I won’t show you how to do that now, you know how by now!) and run php artisan serve in order to start the local webserver. Use Postman to send requests to see if your updates are working.
One thing to note is that this only transforms the output of our database. Your incoming requests are not handled via the transformer. This is due to the design of fractal, and there is a discussion about it here if you’re interested. Instead of “transforming” input in this way, it’s suggested that models and validation are used instead. Next time we’ll look at running some validation on the input we’re sending to create new orders and order items, as well as working out how to apply the API architecture to the models.