05 Feb 2020
cat: API, Laravel, PHP
0 Comments

Laravel API and Angular Client Tutorial – Part 3 API Get Photos and Videos

Introduction

In the last tutorials we added OAuth authentication in our Laravel API and had the Angular App login to it. Now we can create a resource URL where we can get the posts with photos and videos. We will format the response with the help of Dingo API’s helper trait and PHP Leagues Fractal package. The URL will also only be secured and can only be accessed when the request has valid access token.

Setup

First we will need our posts table that will contain the title and file. To create a migration and model files run this command.

artisan make:migration create_posts_table
artisan make:model Post

database/migrations/2020_02_03_162853_create_posts_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('title');
            $table->string('file');
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

The table is straight forward. We got a title, file, and each posts will be belong to a user.

app/Post.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

For the post model, we defined a relationship called user. We will use this to show the users or authors to the response easily.

For now we can add a sample data using SQL insert.

insert into posts (user_id, title, file, created_at, updated_at) values (1, 'Hello World', '2020/test.png', now(), now());

We can add the test.png in public/posts/2020/test.png.

Posts Resource URL

In the first tutorial where we setup the OAuth authentication, we also created a sample resource URL with a static data. You can remove that and replace it like this.

routes/api.php

$api = app(\Dingo\Api\Routing\Router::class);

$api->group([
    'namespace' => 'App\Http\Controllers',
    'version' => 'v1',
    'middleware' => 'api.auth',
    'scopes' => ['read_user_data', 'write_user_data'],
], function (\Dingo\Api\Routing\Router $api) {

    $api->get('posts', 'PostController@index');

});

Before we can create our controller, let us first update the base controller to use the Dingo\Api\Routing\Helpers trait. This will use PHP League Fractal behind the scenes to format the response. All we do is give the models to format and the transformer class.

This is what App\Http\Controllers\Controller.php looks like.

namespace App\Http\Controllers;

use Dingo\Api\Routing\Helpers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use Helpers, AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

Let us create the PostController for now and just return models for now without transformers.

artisan make:controller PostController

app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

class PostController extends Controller
{
    public function index()
    {
        return \App\Post::all();
    }
}

To test with Postman, you can use the Password Credentials.

Laravel API and Angular Client Tutorial – Part 3 API Viewing and Photos and Videos

Laravel API and Angular Client Tutorial – Part 3 API Viewing and Photos and Videos

Transformers

Next we will create a base transformer that will add attributes to the child transformer. This will be helpful in cases you want all transformer to have certain attributes or you want to update all transformers.

app/Transformers/Transformer.php

namespace App\Transformers;

use League\Fractal\TransformerAbstract;

abstract class Transformer extends TransformerAbstract
{
    protected function _transform($model, $data)
    {
        if (!empty($model->id)) {
            $data['id'] = $model->id;
        }
        if (!empty($model->created_at)) {
            $data['created_at'] = $model->created_at->format('Y-m-d H:i:s');
        }
        if (!empty($model->updated_at)) {
            $data['updated_at'] = $model->updated_at->format('Y-m-d H:i:s');
        }
        return $data;
    }
}

Now we can create the Post and User transformer.

app/Transformers/UserTransformer.php

namespace App\Transformers;

use App\User;

class UserTransformer extends Transformer
{
    public function transform(User $user)
    {
        return $this->_transform($user, [
            'name' => $user->name,
            'email' => $user->email,
        ]);
    }
}

app/Transformers/PostTransformer.php

namespace App\Transformers;

use App\Post;

class PostTransformer extends Transformer
{

    protected $availableIncludes = ['user'];

    public function transform(Post $post)
    {
        return $this->_transform($post, [
            'title' => $post->title,
            'file' => $post->file,
        ]);
    }

    public function includeUser(Post $post)
    {
        return $this->item($post->user, new UserTransformer(), 'user');
    }
}

In the PostTransformer, we have an available includes named “user”. This will allow the resource to handle “includes” query parameter and will return the user to the response as well. The parameter is optional as well. This will be helpful for the consumer of the API to have an option to load the related objects in one call instead of multiple calls.

We need to update the PostController to use the transformers.

public function index()
{
    return $this->response->paginator(\App\Post::paginate(), new PostTransformer(), ['key' => 'post']);
}

Repository

It will be helpful if we have a place to contain the queries because when projects gets complicated, you don’t have repetitive queries everywhere. This will also reduce the codes in the controllers. I posted a brief tutorial about repository pattern in Laravel here.

app/Persistence/Repositories/IRepository.php

namespace App\Persistence\Repositories;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

interface IRepository
{
    /**
     * @param int $id
     * @return Model|null
     */
    public function findOne(int $id): Model;

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @return Model|null
     */
    public function findOneBy(array $criteria, array $orderBy = null): Model;

    /**
     * @return Model[]|Collection
     */
    public function findAll();

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @param int|null $limit
     * @param int|null $offset
     * @return Model[]|Collection
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null);
}

app/Persistence/Repositories/IPostRepository.php

namespace App\Persistence\Repositories;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;

interface IPostRepository extends IRepository
{
    /**
     * @param int $pageSize
     * @param array $criteria
     * @param array|null $orderBy
     * @return LengthAwarePaginator
     */
    public function findPaginate($pageSize = 10, array $criteria = null, array $orderBy = null);
}

app/Persistence/Eloquent/Repository.php

namespace App\Persistence\Eloquent;

use App\Persistence\Repositories\IRepository;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

abstract class Repository implements IRepository
{
    protected abstract function getQueryBuilder(): Builder;

    /**
     * @param int $id
     * @return Model|null
     */
    public function findOne(int $id): Model
    {
        return $this->getQueryBuilder()->find($id);
    }

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @return Model|null
     */
    public function findOneBy(array $criteria, array $orderBy = null): Model
    {
        $query = $this->getQueryBuilder()
            ->whereRowValues(array_keys($criteria), '=', array_values($criteria));
        if (!empty($orderBy)) {
            foreach ($orderBy as $column => $direction) {
                $query->orderBy($column, $direction);
            }
        }
        return $query->first();
    }

    /**
     * @return Model[]|Collection
     */
    public function findAll()
    {
        return $this->getQueryBuilder()
            ->get();
    }

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @param int|null $limit
     * @param int|null $offset
     * @return Model[]|Collection
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
    {
        $query = $this->getQueryBuilder()
            ->whereRowValues(array_keys($criteria), '=', array_values($criteria));
        if (!empty($orderBy)) {
            foreach ($orderBy as $column => $direction) {
                $query->orderBy($column, $direction);
            }
        }
        if (isset($limit)) {
            $query->limit($limit);
        }
        if (isset($offset)) {
            $query->offset($limit);
        }
        return $query->get();
    }
}

app/Persistence/Eloquent/PostRepository.php

namespace App\Persistence\Eloquent;

use App\Persistence\Repositories\IPostRepository;
use App\Post;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;

class PostRepository extends Repository implements IPostRepository
{
    protected function getQueryBuilder(): Builder
    {
        return Post::query();
    }

    /**
     * @param int $pageSize
     * @param array $criteria
     * @param array|null $orderBy
     * @return LengthAwarePaginator
     */
    public function findPaginate($pageSize = 10, array $criteria = null, array $orderBy = null)
    {
        $query = $this->getQueryBuilder();
        if (!empty($criteria)) {
            $query->whereRowValues(array_keys($criteria), '=', array_values($criteria));
        }
        if (!empty($orderBy)) {
            foreach ($orderBy as $column => $direction) {
                $query->orderBy($column, $direction);
            }
        } else {
            $query->latest();
        }
        return $query->paginate($pageSize);
    }
}

So in our base repository, we just added a few useful methods that applications usually use. We then extended an create a specific repository for the posts that just returns pagination.

Lastly we need to register the repositories to the container.

app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            \App\Persistence\Repositories\IPostRepository::class,
            \App\Persistence\Eloquent\PostRepository::class
        );
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }
}

We an now use the repository to the posts controller.

app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

use App\Persistence\Repositories\IPostRepository;
use App\Transformers\PostTransformer;

class PostController extends Controller
{
    public function index(IPostRepository $repository)
    {
        return $this->response->paginator($repository->findPaginate(), new PostTransformer(), ['key' => 'post']);
    }
}

Testing it out

Laravel API and Angular Client Tutorial – Part 3 API Viewing and Photos and Videos

Response

{
    "data": [
        {
            "type": "post",
            "id": "1",
            "attributes": {
                "title": "Hello World",
                "file": "2020/test.png",
                "created_at": "2020-02-03 19:13:38",
                "updated_at": "2020-02-03 19:13:38"
            },
            "relationships": {
                "user": {
                    "data": {
                        "type": "user",
                        "id": "1"
                    }
                }
            }
        }
    ],
    "included": [
        {
            "type": "user",
            "id": "1",
            "attributes": {
                "name": "Test User",
                "email": "testuser01@example.com",
                "created_at": "2020-01-19 13:52:59",
                "updated_at": "2020-01-19 13:52:59"
            }
        }
    ],
    "meta": {
        "pagination": {
            "total": 1,
            "count": 1,
            "per_page": 10,
            "current_page": 1,
            "total_pages": 1
        }
    },
    "links": {
        "self": "http://api.tutorial.test/api/posts?page=1",
        "first": "http://api.tutorial.test/api/posts?page=1",
        "last": "http://api.tutorial.test/api/posts?page=1"
    }
}

Thanks for reading!

References

Be the first to write a comment.

Post A Comment