apt get life

Life around technology

  • Technology
    • Guides
    • Linux
    • Development
      • Laravel
    • Misc
    • Raspberry Pi
  • Writing
  • Crafts
    • Crochet
    • Model Making
    • Painting
  • Privacy Policy
You are here: Home / Archives for Technology / Development / Laravel

Laravel Docker Development Environment With PHP7

2020/07/27 by sudo 1 Comment

Running specific PHP versions for Laravel can be quite useful, especially when working with legacy applications. I work on a range of different solutions with different versions of PHP and Laravel versions. To save me time reconfiguring my local environment’s PHP version and to better represent the live systems, I have opted for Docker based development environments. Here’s what I am aiming for:

  • Customisable PHP versions
    • Including libraries like Imagick and XDebug to make dev easier
  • Self contained database instance
  • Supporting queue worker, so I can test queues work locally
  • Email catching, so I can test email notifications locally
  • Redis, for queue management
  • The Laravel Scheduler working

In order to achieve this, I’ve opted to use a docker-compose environment with custom docker PHP file. This defines the PHP version as well as any extra libraries in it that I need for the project. Then the project files (source code of the Laravel application) can be mounted as a volume. By mounting the project’s source code, it’s available for an editor on the host machine, while also being available for the PHP code to execute.

Let’s start by defining the project structure:

.
├── .docker
│   ├── Dockerfile.app
│   └── nginx
│       └── default.conf
├── docker-compose.yml
└── src

This structure tends to keep the Docker configuration and extra files neater, since they’re self-contained in a `.docker` directory. The custom PHP docker file (Dockerfile.app) is contained here, as is a subdirectory for Nginx, the webserver I’ll be using. Only the docker-compose file needs to be in the parent folder.

Lets start with the docker file. You’ll need to find your host user and group ID. On Linux (and presumably Mac) you can find this by running id -u and id -g. Normally they’re both 1000. Replace the ARG entries in the docker file if your IDs are different.

If you’ve not created the directory structure already, do it now:

mkdir -p .docker/nginx

Now create the Docker file, I’m using Nano but you can use whatever editor you want: nano .docker/Dockerfile.app

FROM php:7.2-fpm

# Define the User and Group ID for this docker file. This should match your host system UID and GID.
ARG UID=1000
ARG GID=1000

# Set working directory for future docker commands
WORKDIR /var/www/html

# Install dependencies
RUN apt-get update && apt-get install -y --quiet ca-certificates \
    build-essential \
    mariadb-client \
    libpng-dev \
    libxml2-dev \
    libxrender1 \
    wkhtmltopdf \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    curl \
    libmcrypt-dev \
    msmtp \
    iproute2 \
    libmagickwand-dev

# Clear cache: keep the container slim
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Xdebug
# Note that "host.docker.internal" is not currently supported on Linux. This nasty hack tries to resolve it
# Source: https://github.com/docker/for-linux/issues/264
RUN ip -4 route list match 0/0 | awk '{print $3" host.docker.internal"}' >> /etc/hosts

# Install extensions: Some extentions are better installed using this method than apt in docker
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ \
    && docker-php-ext-install \
        pdo_mysql \
        mbstring \
        zip \
        exif \
        pcntl \
        xml \
        soap \
        bcmath \
        gd

# Install Redis, Imagick xDebug (Optional, but reccomended) and clear temp files
RUN pecl install -o -f redis \
    imagick \
    xdebug \
&&  rm -rf /tmp/pear \
&&  docker-php-ext-enable redis \
    imagick \
    xdebug

# Install composer: This could be removed and run in it's own container
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# xdebug.remote_connect_back = true does NOT work in docker
RUN echo '\n\
[Xdebug]\n\
xdebug.remote_enable=true\n\
xdebug.remote_autostart=true\n\
xdebug.remote_port=9000\n\
xdebug.remote_host=docker.host.internal\n'\
>> /usr/local/etc/php/php.ini

RUN echo "request_terminate_timeout = 3600" >> /usr/local/etc/php-fpm.conf
RUN echo "max_execution_time = 300" >> /usr/local/etc/php/php.ini

# Xdebug
# Note that "host.docker.internal" is not currently supported on Linux. This nasty hack tries to resolve it
# Source: https://github.com/docker/for-linux/issues/264
#RUN ip -4 route list match 0/0 | awk '{print $3" host.docker.internal"}' >> /etc/hosts
RUN ip -4 route list match 0/0 | awk '{print "xdebug.remote_host="$3}' >> /usr/local/etc/php/php.ini

# Add user for laravel application
RUN groupadd -g $GID www
RUN useradd -u $UID -ms /bin/bash -g www www

# Make sure permissions match host and container
RUN chown www:www -R /var/www/html

#  Change current user to www
USER www

# Copy in a custom PHP.ini file
# INCOMPLETE/UNTESTED
#COPY source /usr/local/etc/php/php.ini

# We should do this as a command once the container is up.
# Leaving here incase someone wants to enable it here...
#RUN composer install && composer dump-autoload -o

I’ve left in some commented commands, which can be uncommented and customised if needed. The file comments should also help you make any changes as needed, but the file should work for you as is.

Next, lets create the nginx configuration file nano .docker/nginx/default.conf

server {
    listen 80 default_server;

    root /var/www/html/public;

    index.php index index.html index.htm;

    charset utf-8;

    location = /favicon.ico { log_not_found off; access_log off; }
    location = /robots.txt  { log_not_found off; access_log off; }

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        fastcgi_read_timeout 3600;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }

    error_page 404 /index.php;

    location ~ /\.ht {
        deny all;
    }
}

The most important part of this file is the fastcgi_pass php:9000; line. This tells nginx in it’s container where to find PHP running in it’s container. You’ll see that tie in the docker compose file.

Create the docker-compose.yml file nano docker-compose.yml

version: '3'

services:

    # Nginx web server
    nginx:
        image: nginx:stable-alpine
        ports:
            # OPTIONAL: change the port number before the colon ":" to alter we traffic port
            - "8080:80"
        volumes:
            - ./src:/var/www/html
            - ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
        depends_on:
            # for this container to run, wait until PHP and MYSQL are running
            - php
            - mysql
        networks:
            # OPTIONAL: change or remove the network name (do this for all containers)
            - laravel

    # MySQL database server
    mysql:
        image: mysql:5.7
        restart: unless-stopped
        tty: true
        ports:
            # OPTIONAL: Change the port number before the colon ":" to alter where MySQL binds on the host
            # Allow connections to MySQL from the host (MySQL Workbench, DataGrip, etc) on port 33060
            # WARNING: do not expose in production!
            - "3306:3306"
        environment:
            # OPTIONAL: Change MySQL credentials
            MYSQL_ROOT_PASSWORD: secret
            MYSQL_DATABASE: laravel
            MYSQL_USER: laravel
            MYSQL_PASSWORD: secret
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
        networks:
            - laravel
        volumes:
            # Persist MySQL data with a docker volume (see end of file)
            - mysql_data:/var/lib/mysql

    # Custom PHP image for Laravel
    php:
        build:
            context: .
            dockerfile: ./.docker/Dockerfile.app
        volumes:
            - ./src:/var/www/html
            # Load a custom PHP.ini file
            #- ./.docker/php/php.ini:/usr/local/etc/php/php.ini
        #command: ip -4 route list match 0/0 | awk '{print $$3" host.docker.internal"}' >> /etc/hosts
        networks:
            - laravel

    # Redis, for caching and queues (Optional)
    redis:
        image: redis:5-alpine
        restart: unless-stopped        
        # OPTIONAL: change or open up Redis port binding.
        # Disabled by default for security. Redis should not be exposed to the world!
        # your other containers should still be able to access it without this enabled
        #ports:
            #- 6379:6379
        networks:
            - laravel

    # Laravel Horizion (Optional)
    # NOTE: if you're not running horizon, you should delete this stanza or you'll get errors
    horizon:
        build:
            context: .
            dockerfile: ./.docker/Dockerfile.app
        restart: unless-stopped
        command: /bin/bash -c 'while [ 0 -lt 1 ] ; do php artisan horizon; sleep 60; done'
        networks:
            - laravel
        volumes:
            - ./src:/var/www/html

    # Laravel Scheduler (Optional)
    scheduler:
        build:
            context: .
            dockerfile: ./.docker/Dockerfile.app
        restart: unless-stopped
        command: /bin/bash -c 'while [ 0 -lt 1 ] ; do php artisan schedule:run >> /dev/null 2>&1 ; sleep 60; done'
        networks:
            - laravel
        volumes:
            - ./src:/var/www/html

    # Default Queue Worker (Optional)
    worker-default:
        build:
            context: .
            dockerfile: ./.docker/Dockerfile.app
        restart: unless-stopped
        command: /bin/bash -c 'while [ 0 -lt 1 ] ; do php artisan queue:work --tries=3 --timeout=90 --sleep=10; done'
        networks:
            - laravel
        volumes:
            - ./src:/var/www/html

    # Mailhug (Optional, mail-catcher)
    # Comment out or delete this if you don't want to use it
    mailhog:
        image: mailhog/mailhog
        networks:
            - laravel
        ports:
            # Uncomment to allow host access to SMTP (not sure why you'd want to?!)
            # your containers on the same network can still access this without the binding
            # - 1025:1025 # smtp server
            # OPTIONAL: Change the port number before the colon ":" to alter where the Mailhog UI can be accessed
            - 8025:8025 # web ui

networks:
    # A network for the laravel containers
    laravel:


# Persist the MySQL data
volumes:
    mysql_data:

This is quite a big file. Each container is defined inside the service block. Most are provided containers from dockerhub. There’s a few important things to know (which are mostly commented in the file).

The Nginx container has ports exposed. I’ve set these to 8080 externally, mapping to port 80 internally. So to access the site in your browser navigate to http://localhost:8080. The next thing the container does is mount two volumes. The first is the source code for your application, the second is the default.conf nginx file written above.

The MySQL container has port 3306 count to the host, allowing access from a MySQL management tool such as MySQL Workbench, DataGrip or DBeaver. You absolutely should not run this on a production server without firewalling it. Infact this whole environment is designed for local development, but this particularly needs raised as a point for anyone adapting this for production. Do not expose MySQL to the world! Other settings of interest here are the MYSQL_ segments. You can use these to define your username, password, database name. Additionally, the configuration mounts a volume to the MySQL database directory which means the data will be persistent until the volume is deleted. You can optionally remove this if you want volatile data that’s deleted on container restart.

The PHP container’s name is important. This relates to the nginx configuration file, where the fast_cgi parameters was defined. If you change the container definition form php: to something else, you’ll need to update it in the nginx default.conf as well as elsewhere in this file. The PHP image also needs to have a volume for the source code, and this needs to be the same path as the nginx container. Because this is a custom docker file, this needs built by docker-compose instead of just pulling an image. You can of course create this image and upload it to somewhere like dockerhub and include it from there, but I like to keep the environment customisable without messing around with external docker hubs.

The other containers are entirely optional. If you’re not running Horizon, then just remove or comment out that block. Same with the other remaining containers.

Next thing to do is create a new Laravel install in the src directory, or copy in an existing Laravel repo. Generally I install a new Laravel instance using composer like this:
`

composer create-project --prefer-dist laravel/laravel src

Now all that’s left to do is run docker-compose up -d. It’ll build the PHP image, pull the MySQL and nginx image and start your containers using the ports specified in the docker-compose file. To run composer or artisan commands, simply run docker-compose exec php bash and you’ll be dropped into the web directory on the PHP docker container. From here you can easily run commands such as php artisan key:generate, php artisan migrate and any of the php artisan make: commands.

It’s also possible to version control your src folder. Do this from the host, and not inside a docker container. cd src to go into the source code directory, as it’d be unusual for you to store your dev environment with the application. git init should initialise a new git repository for you to manage as you see fit.

Filed Under: Development, Docker, Guides, Laravel, Technology Tagged With: development, docker, docker-compose, Laravel, PHP development

Laravel 5.2 API Token Authentication

2016/04/30 by sudo

At work I’ve been tasked with improving an API recently, and I decided it would be a good opportunity to take Laravel out for a spin. I’ve been keen on learning more about laravel and it’s API capabilities which are supposedly very strong, although I have noted that there’s not much documentation around them. The existing API is flat PHP and uses token based authentication. This allows users to authenticate with a string “api_key” in the request URL, in the header or in the body of the JSON request. I decided that instead of trying to get existing users to upgrade to something like oAuth (for which there are some interesting plugins https://packagist.org/packages/lucadegasperi/oauth2-server-laravel), I’d just implement the same token based authentication model for the revised API in Laravel. There are already advantages to using Laravel for APIs – it highly encourages a restful approach, as for Laravel 5.2 it includes rate limiting out of the box and allows for route prefixing, so it is possible to have multiple endpoints in one Laravel application.

Setting up token based authenticaton in Laravel is so poorly documented that it took me a while to work out how it is achieved.

1. User API Tokens

Users need to have an API token to be associated with them in order to allow the authentication model to work. This is easy enough to add by editing the user migration in your laravel installation.

// Store an API key for this user.
$table->string('api_token', 60)->unique();

This allows you to store a 60 character unique API Token for each user.

2. Setting up API Authentication

There are several ways you can now call API Token authentication for your application. Probably the best is to use middleware in your routes file:

Route::group([
    'prefix' => 'api',
    'middleware' => 'auth::api'
    ], function() {
    Route::resource('fruit', FruitController);
});

Now any time requests are made to the route group, the API authentication method will be called. This includes token based authentication (now defined in the users table) as well as the API rate limiting.

3. Making API Requests

You can now submit your API requests to see if the Laravel token authentication is working. To do this you can submit “api_token” as either a GET or POST paramiter. There’s also hidden away the option to have it set as a header, however this requires you to use an Authorization header:

Key: ‘Authorization’

Value: ‘Bearer [token]’

Check out the code here:

https://github.com/laravel/framework/blob/c04159dee4a47b0d7cd508ab720932121927b1b3/src/Illuminate/Http/Request.php#L815-L822

and here:

https://github.com/laravel/framework/blob/master/src/Illuminate/Auth/TokenGuard.php#L81-L94

 

Filed Under: Laravel Tagged With: API, Laravel, php

Getting started with Laravel 5 & Dingo API: 4. Transformers

2016/04/01 by sudo

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:

  1. We’re exposing our database architecture – people can see orders have fields “order_ref”, “recipient_id”, etc.
  2. Our index functions are using “all()”, so they get all results from the database.
  3. We’re not validating our data before adding it to the database.
  4. 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.

 

Filed Under: Development, Laravel, Technology Tagged With: API, Dingo API, Getting started with Laravel 5 & Dingo API, Laravel, larvel 5, Transformers

Getting started with Laravel 5 & Dingo API: 3. Controllers

2016/03/24 by sudo

This is part 3 of the Laravel 5 & Dingo API series, in which we’re building an API to receive orders from 3rd parties and ship them to recipients. Last time we covered setting up the database, creating migrations and setting up models. This time we’re going to focus on Laravel’s controllers and how we get them working with Dingo API in order to create, read and update data stored in our database.

Controllers

I’m going to start with a variant controller as it has more information that can be returned with a get request and we wouldn’t allow 3rd parties accessing the API to create any variants. This makes it much simpler to build.

With Dingo API, we should specify a base controller to pull all of the helper functions into our individual controllers using inheritance. To do this run:

php artisan make:controller api\\BaseController

Open the BaseController.php file in Atom and add edit it to look like the following:

<?php

namespace App\Http\Controllers\api;

use Dingo\Api\Routing\Helpers;
use Illuminate\Routing\Controller;

class BaseController extends Controller
{
    use Helpers;
}

This is simply inheriting the Controller class, then using the Helpers and creating a new BaseClass to extend from in our API.

We’re going to create a VariantsController, and to keep things organised lets also make it in a subfolder of the application. From the command line run:

php artisan make:controller api\\VariantsController --resource

Now open the controller in Atom and after the use statements at the top of the file add one for the Variants model:

use App\Variants;

You’ll notice because we specified the “–resource” flag at the end of our command line action, the controller has been populated with a skeleton framework of RESTful functions. In the “index” function lets add some code to return all of our variants from the database:

public function index()
{
   return $this->response->array(Variants::all());
}

Before we can test this, lets update our routes.php file to use the new controller:

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

$api->version('v1', function ($api) {
    $api->get('/', function() {
        return ['test' => true];
    });
    $api->get('/variants', 'App\Http\Controllers\api\VariantsController@index');
});

So, now if you navigate to /api/variants (http://localhost:8000/api/variants/) you should get Dingo API returning all variants in the database. (note you may need to run php artisan serve from the command line first, and if you didn’t seed the database you’ll get an empty response [] ). If you didn’t seed your database, why not add some records and see what responses you get, for example I’ve added a variant and the response is now:

{
  "variants": [
    {
      "id": 1,
      "size": "small",
      "brand": "fashion",
      "type": "hoodie",
      "colour": "black",
      "design": "blank",
      "created_at": "-0001-11-30 00:00:00",
      "updated_at": "-0001-11-30 00:00:00"
    }
  ]
}

At this point, we know our routing is working, and that we’re able to connect to and query a database as well as returning a response to the user. Lets create the Orders and Items controllers:

php artisan make:controller api\\ItemsController --resource
# Controller created successfully.
php artisan make:controller api\\OrdersController --resource
# Controller created successfully.

Lets fill in the functionality to get all items first. Open up the ItemsController in Atom and add the use statement, update it to use the dingo base controller as well as the index function code:

use App\Items;

class ItemsController extends BaseController
public function index()
{
   return $this->response->array(Items::all());
}

While we’re in the items controller, lets add the ability to find a single item in the show method

public function show($id)
{
    return $this->response->array(Items::find($id));
}

As part of the URL, we will pass the item ID, which allows us to do a database search for that item and return the response. If no item is found it’ll return an empty array.

We can save items like so:

public function store(Request $request)
{
    $item = new Items;

    $item->item_ref = $request->input('item_ref');
    $item->quantity = $request->input('quantity');
    $item->variant_id = $request->input('variant_id');

    $item->save();
}

We can also allow updates for an item:

public function update(Request $request, $id)
{
    $item = Items::find($id);

    $item->item_ref = $request->input('item_ref');
    $item->quantity = $request->input('quantity');
    $item->variant_id = $request->input('variant_id');

    $item->save()
}

And finally delete an item:

public function destroy($id)
{
   $item = Items::find($id);
   if ($item->delete()) {
            return $this->response->array(['id' => $id, 'status' => 'deleted']);
   }
}

in the routes.php file, add the methods that we want for the items controller

    // Items
    $api->get('/items', 'App\Http\Controllers\api\ItemsController@index');
    $api->post('/items', 'App\Http\Controllers\api\ItemsController@store');
    $api->get('/items/{id}', 'App\Http\Controllers\api\ItemsController@show');
    $api->patch('/items/{id}', 'App\Http\Controllers\api\ItemsController@update');
    $api->destroy('/items/{id}', 'App\Http\Controllers\api\ItemsController@destroy');

Now you should be able to use Postman to create items:

POST | http://localhost:8000/api/items/

{
    "item_ref": "Test1",
    "quantity": 5,
    "variant_id": 1
}

Select items

GET | http://localhost:8000/api/items/

{
  "items": [
    {
      "id": 1,
      "item_ref": "Test1",
      "quantity": "100",
      "variant_id": "1",
      "created_at": "2016-03-09 16:00:52",
      "updated_at": "2016-03-09 16:07:34"
    }
]

update items

PATCH | http://localhost:8000/api/items/1

{
    "item_ref": "Test1",
    "quantity": 100,
    "variant_id": 1
}

and delete items:

DELETE | http://localhost:8000/api/items/2

{
  "id": "2",
  "status": "deleted"
}

Have a play with the items controller and Postman to make sure your routes and actions are working as expected before moving on. These implementations are not perfect, but it’s enough to get started. Later we’ll see how we can perform validation and even use transformers to alter requests.

The Orders Controller

The orders controller is going to be a duplicate of the items controller to begin with. All of the functionality we added into it we will add into this.

use App\Orders;

class OrdersController extends BaseController

Adding the index method to list all orders

public function index()
{
    return $this->response->array(Orders::all());
}

Adding the store method

public function store(Request $request)
{
    $order = new Orders;

    $order->order_ref = $request->input('order_ref');
    $order->recipient_id = $request->input('recipient_id');
    $order->shipping_method = $request->input('shipping_method');

    if ( $order->save() ) {
        return $this->response->created();
    } else {
        return $this->response->errorBadRequest();
    }
}

Adding the show method

public function show($id)
{
    return $this->response->array(Orders::find($id));
}

The update method

public function update(Request $request, $id)
{
    $order = Orders::find($id);
    $order->order_ref = $request->input('order_ref');
    $order->recipient_id = $request->input('recipient_id');
    $order->shipping_method = $request->input('shipping_method');

    $order->save();
}

and the destroy method

public function destroy($id)
{
    $order = Orders::find($id);
    if ($order->delete()) {
        return $this->response->array(['id' => $id, 'status' => 'deleted']);
    }
}

Finally adding the routes to routes.php

// Orders
$api->get('/orders', 'App\Http\Controllers\api\OrdersController@index');
$api->post('/orders', 'App\Http\Controllers\api\OrdersController@store');
$api->get('/orders/{id}', 'App\Http\Controllers\api\OrdersController@show');
$api->patch('/orders/{id}', 'App\Http\Controllers\api\OrdersController@update');
$api->delete('/orders/{id}', 'App\Http\Controllers\api\OrdersController@destroy'

Now is a good time to commit what we’ve done in git, before moving onto refactoring it.

git add -A
git commit -m "created items, orders and variants controllers with basic functionality"

Filed Under: Development, Laravel, Technology Tagged With: API, Dingo API, Getting started with Laravel 5 & Dingo API, Laravel, larvel 5

Getting started with Laravel 5 & Dingo API: 2. Databases and Migrations

2016/03/17 by sudo

This is part 2 of the Laravel 5 & Dingo API series, in which we’re building an API to receive orders from 3rd parties and ship them to recipients. Last time we covered setting up the environment and making a test API call using Dingo API. This time around, we’ll be looking at what the database architecture could be like, and how we create it with migrations and setting up models with Eloquent ORM in Laravel to allow the API to access our database.

Creating a Database

As with most applications you’ll ever write or interact with, the database is key and it’s design and organisation will be wholly dependent on the application you’re crafting. Right now, I’m going to look at an order API. Lets ponder the database architecture for a moment.

What information do we need to store in order to achieve an order API?

  • Who’s ordered something (eg the “customer” that we’re going to bill, possibly including billing information)
  • Where are we shipping it to
  • What items have been ordered
    • size
    • colour
    • brand
    • type
    • design
  • How many items have been ordered
  • What status an order is (has it been dispatched)

We’re going to have an order, which can consist of many items. Each item can be of a particular variant (think size, colour or brand). This gives us this as a rough database architecture:

API_DBA

One Order has many Items

One Item belongs to one Order

One Item has one Variant

One Variant can be in many Order Items

We could also think about an order having a purchaser and a recipient. It may be too complex for the initial version of this API, but just so you can optionally add this my basic logic is:

One Order belongs to one User

One User has many Orders

One Order can be dispatched to one Recipient

One Recipient can receive many Orders

In my case I’m going to create the following objects:

[table caption=”Orders” ]
Name, Description
ID, our unique identifier for this order.
order_ref, this is the customer reference for the order
recipient, this is going to be an array of recipient address data.
shipping_method, the shipping method to be used for this order
recipient_id, the FK to the recipients table so we know who to post the order to.
[/table]

[table caption=”Items” ]
Name, Description
ID, our unique identifier for this item.
item_ref, this is the customer reference for the item
quantity, number if this item to be sent
variant_id, the Foreign key used to find the variant information for the order
[/table]

[table caption=”Variants” ]
Name, Description
ID, our unique identifier for this item. Usually an SKU
size, size of the variant
brand, brand of the variant
type, type of variant, eg hoodie, t-shirt
colour, the colour of the garment
design, the design on the garment (if any)
[/table]

(just for reference, if the tutorial were to be expanded, the recipient’s table would be included too. This provides better data normalisation than just listing the recipient of an order inside the order table.)

[table caption=”Recipients” ]
Name, Description
ID, our unique identifier for this order.
name, name of the recipient for shipping
address_1, first line of their address
address_2, second line of their address
city, their town or city
region, the county or state
country_code, a 2 digit ISO standard country code for international orders
post_code, their post code
phone, their phone number which will be given to the shipping company
email, optionally their email address for marketing
[/table]

Migrations

Laravel uses a feature called Migrations to create database schemas. This is fantastic when you’re working with multiple developers as you can architect the database in flat files and “migrate” changes each time you pull down a new version of the code base. It also makes it easier than writing out all of the database tables in SQL or using a tool like MySQL Workbench to create them. We’re going to use the power of the migrations in Laravel to create our API table layout.

From the command line, lets create a migration for each table we’re going to use

php artisan make:migration create_table_orders
php artisan make:migration create_table_items
php artisan make:migration create_table_variants

You’ll have three new files created in your project folder in database/migrations. These files will be prefixed with a date time stamp, for example php artisan make:migration create_table_orders. You’ll also note that there are some preexiting migrations for users and password resets. You’re welcome to poke around these and see what they’re doing before moving on.

Lets start with the orders migration. Open it up in Atom so we can add information about our table into it. What you’ll be presented with is a class containing an up function and a down function, which equate to create and delete. When we run migrations it creates the table, if we reset it’ll delete the table in our database. Lets add our table elements:

class CreateTableOrders extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            // This is an auto incrementing primary key
            $table->increments('id');
            // This is a string or integer provided by the customer
            $table->string('order_ref');
            // This would link to our recipients table and be a foreign key, but we're not implementing it in our tutorial
            $table->integer('recipient_id');
            // This is a string indicating what shipping method we're going to use. Technically this should be split into it's own table for shipping methods, but for now it'll do.
            $table->string('shipping_method');
            // Timestamps are really useful for data monitoring. They let you see the created_at and updated_at times for the record.
            $table->timestamps();
        });
    }

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

Our items table:

class CreateTableItems extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            // This is an auto incrementing primary key
            $table->increments('id');
            // This is a string or integer provided by the customer
            $table->string('item_ref');
            // How many of this item are in this order?
            $table->integer('quantity');
            // This is a Forigin Key to the variants table, allowing us to know which of our item variants we want to provide in the order.
            $table->string('variant_id');
            // Timestamps are really useful for data monitoring. They let you see the created_at and updated_at times for the record.
            $table->timestamps();
        });
    }

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

And finally our Variants table:

class CreateTableVariants extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('variants', function (Blueprint $table) {
            // This is an auto incrementing primary key
            $table->increments('id');
            // OPTIONALLY:
            // an item can have an SKU. This is a unique id and is great for a primary key, but there are ongoing debates in the MySQL community over this being a good or bad idea. Auto incrementing fields are easier to index after all.
            //
            // If you wanted to include an SKU you could do it by using the following:
            //$table->string('sku')->unique();
            // This is the garment size
            $table->string('size');
            // This is the garment brand
            $table->string('brand');
            // This is the garment type
            $table->string('type');
            // This is the garment colour
            $table->string('colour');
            // This is the garment design
            $table->string('design');
            // Timestamps are really useful for data monitoring. They let you see the created_at and updated_at times for the record.
            $table->timestamps();
        });
    }

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

Now, before we can begin playing with migrations, you need to have your database connection setup. If you’re happy that it is then lets go back to the command line and run:

php artisan migrate

You should get a message similar to the following in response:

Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2016_03_06_174944_create_table_items
Migrated: 2016_03_06_174944_create_table_orders
Migrated: 2016_03_06_174945_create_table_variants

If you got anything else, including an error, then you need to take a look at what it’s telling you and correct it before continuing.

Optionally at this point you can open up a database editor and check it’s all there as you expected it to be. Since I’m calling this another milestone in the project, I’m also going to commit my changes.

git add -A
git commit -m "initial database migrations created"

The migration tool is quite powerful and will let you roll back migrations, or even reset them (delete the tables – remember those down functions in our migration files – that’s when these are called) or refresh, which resets then re-migrates the tables. Take a look at the Laravel docs on migrations for more information.

Seeders

A quick note on seeders. We can actually create a few records in the database using a seeder in Laravel, which is great for testing things like API get requests. Seeders are created on the command line using php artisan make:seeder. It might be worth adding some for our tables, but right now I won’t. You can always research if you’re interested in the subject.

Creating our Models

Now that the database has been created, we can begin to look at making some API calls to retrieve, add, delete and edit data to them. Most of our work will take place in the App folder form now on.

In order to talk to the database in Laravel we will be using a model. Once again, if you want details of MVC frameworks in general, or what models do in Laravel I encourage you to do your own research. I can’t cover everything here! What we will do is drop back to the command line and create some models for our new tables. I follow the convention of models being named as plural – so “order” is “orders”. This is preference and you don’t need to follow it if you don’t want to.

php artisan make:model Orders
# Model created successfully.
php artisan make:model Items
# Model created successfully.
php artisan make:model Variants
# Model created successfully.

You’ll see the app directory has files named the same as those listed on the command line arguments.Laravel uses Eloquent to mange database queries and relationships. Lets open them up one at a time and define some of these relationships between the tables.

The orders table needs to be related to items. Since one order has many items, we can reflect it in Laravel’s order model like so:

class Orders extends Model
{
    public function items()
    {
        return $this->hasMany('App\Items');
    }
}

Similarly we need to create the reverse of the relationship in the items model:

class Items extends Model
{

    public function order()
    {
        return $this->belongsTo('App\Orders');
    }

}

But, an item can also have a variant, so lets add another relationship to the same items model:

class Items extends Model
{

    public function order()
    {
        return $this->belongsTo('App\Orders');
    }

    public function variant()
    {
        return $this->belongsTo('App\Variants');
    }

}

Take note, that because there’s one variant, and one order for one item, I’ve used singulars as the function names. On the orders model I’ve used a plural “items” as there are many items in an order. This is preference, and adjust as you see fit.

Finally lets load up the variants model and add the items relationship:

class Variants extends Model
{
    public function items()
    {
        return $this->hasMany('App\Items');
    }
}

We’ll see how these relationships benefit us later. For now, lets take a look at further work on the models. A model will use a table of the same name by default, find out more at the Laravel docs website.

I’m also going to allow fillable fields using mass assignment. To do this in each of the models create an array of variables that you’ll allow to be fillable.

The orders class now looks like this:

class Orders extends Model
{
    protected $fillable = ['order_ref', 'recipient_id', 'shipping_method'];
    
    public function items()
    {
        return $this->hasMany('App\Items');
    }
}

Lets add fillable column names to the other models.

Items:

protected $fillable = ['item_ref', 'quantity', 'variant_id'];

Variants:

protected $fillable = ['size', 'brand', 'colour', 'type', 'design'];

That’s us done playing with models right now. Save all that and lets add it to git.

git add -A 
git commit -m "created models for orders, items and variants with fillable fields defined and relationships created"

Filed Under: Development, Laravel, Technology Tagged With: API, Databases, Dingo API, Getting started with Laravel 5 & Dingo API, Laravel, Laravel Migrations, larvel 5

  • 1
  • 2
  • Next Page »

Recent Posts

  • Disable iLO on HP Microserver Gen8
  • Ubuntu Desktop 24.04 Change Wallpaper Settings
  • Customising Ubuntu Desktop 24.04
  • Remove domains from Let’s Encrypt using Certbot
  • Install Jetbrains Toolbox on Ubuntu 22.04

Tags

API auditing crochet data recovery debian debudding development Dingo API docker email Getting started with Laravel 5 & Dingo API hard drive health HP Microserver KVM Laravel larvel 5 lenovo Linux Minion mint netgear nas networking network shares php PHP development Postfix raspberry pi review samba security SMART smartctl smartmontools smb testing traefik ubuntu ubuntu 18.04 ubuntu 20.04 ubuntu 22.04 ubuntu server vagrant Virtual machines xdebug xubuntu

© Copyright 2015 apt get life

 

Loading Comments...