Creating a Framework-Agnostic PHP API for Nuxt

In previous chapters, such as Chapter 8, Adding a Server-Side Framework, and Chapter 9, Adding a Server-Side Database, you learned how to create APIs using Nuxt's default server with Node.js JavaScript frameworks such as Koa and Express. In Chapter 12, Creating User Logins and API Authentication, you learned how to create APIs using an external server with the same Node.js JavaScript framework Koa.

In this chapter, we will guide you through how to create APIs using an external server with PHP: Hypertext Preprocessor (or simply PHP) instead. In Chapter 9Adding a Server-Side Database, you also learned how to use MongoDB to manage the database. However, in this chapter, we will use MySQL instead, which you used with Koa in Chapter 12, Creating User Logins and API Authentication.

Most importantly, in this chapter, you will learn all about PHP standards and the PHP Standards Recommendations (PSRs). In particular, you will learn about PSR-4 for autoloading, PSR-7 for HTTP messages, and PSR-15 for composing middleware components and handling HTTP server requests. We will put together the packages that are based on these PSR standards from different vendors such as Zend Framework and The PHP League to make a framework-agnostic PHP RESTful API for our Nuxt app.

In this chapter, we will cover the following topics:

  • Introducing PHP
  • Understanding HTTP messages and PHP standards
  • Writing CRUD operations with PHP database frameworks
  • Integrating with Nuxt

Let's get started!

Introducing PHP

PHP has come a long way. It existed long before Node.js and was created by Rasmus Lerdorf in 1994. It stood for Personal Home Page originally. The PHP reference implementation is now produced by The PHP Group (https://www.php.net/). PHP was originally developed as a templating language that allowed us to mix HTML with PHP code itself, just like Twig (https://twig.symfony.com/) and Pug (https://pugjs.org/) do these days.

Now, PHP is more than just a templating language. Over the years, it has evolved into a general-purpose scripting language and object-oriented language, especially suited for server-side web development. You can still use it for templating, but we should make use of its full power in modern PHP development. If you want to check out what else PHP can do, visit https://www.php.net/manual/en/intro-whatcando.php.

At the time of writing this book, the current stable version of PHP is 7.4.x. If you are getting started with PHP, start with PHP 7.4. If you are using PHP 7.2 or 7.3, you should consider upgrading it to PHP 7.4 as it contains several bug fixes. For more about the changes in this release, visit https://www.php.net/ChangeLog-7.php.

In this book, we will guide you through how to install or upgrade to PHP 7.4 on Ubuntu with Apache2 support. Let's get started!

Installing or upgrading PHP

If you are on macOS, please use this guide: https://phptherightway.com/mac_setup. If you are on Windows, then please use this guide: https://phptherightway.com/windows_setup.

We're using an Apache2 HTTP server but you can use an Nginx HTTP server if you have it installed on your machine already. Now, follow these simple steps to install PHP:

  1. Run the following commands to update the local packages on your Ubuntu server and install Apache2:
$ sudo apt update
$ sudo apt install apache2
  1. After installing Apache2, verify it with the -v option:
$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built: 2019-08-14T14:36:32

You can use the following commands to stop, start, and enable the Apache2 service so that it always starts up when the server boots up:

$ sudo systemctl stop apache2
$ sudo systemctl start apache2
$ sudo systemctl enable apache2

You can use the following command to check the status of Apache2:

$ sudo systemctl status apache2

You should always get active (running) as the output in your terminal:

apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2020-08-06 13:17:25 CEST; 52min ago
//...
  1. Run the following commands to install PHP 7.4:
$ sudo apt update
$ sudo apt install php
  1. You should also install PHP 7.4-related modules and extensions that may be needed when developing PHP apps:
$ sudo apt install -y php7.4-{bcmath,bz2,curl,gd,intl,json,mbstring,xml,zip,mysql}
  1. Disable PHP 7.3 (if you are on PHP 7.3) and then enable PHP 7.4:
$ sudo a2dismod php7.3
$ sudo a2enmod php7.4

If you are installing PHP for the first time, then you don't have to disable the older version. If you want to uninstall PHP and all its related modules, you can use the following command:

$ sudo apt-get purge 'php*'
  1. Restart the Apache2 and PHP services:
$ sudo service apache2 restart
  1. Now, you can verify the PHP you just installed with the following command:
$ php -v

You should get the following version information:

PHP 7.4.8 (cli) (built: Jul 13 2020 16:46:22) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.8, Copyright (c), by Zend Technologies

Now that you have Apache2 and PHP 7.4 installed, the next thing you should do is configure PHP. We'll do this in the next section.

Configuring PHP 7.4

Now that Apache2 and PHP are installed, you may want to configure PHP so that you can use it according to what suits your PHP apps. The default PHP configuration file is located at /etc/php/7.4/apache2/php.ini, so follow these steps to configure your version of PHP 7.4:

  1. Run the following command to edit or configure PHP 7.4:
$ sudo nano /etc/php/7.4/apache2/php.ini

You may need to change the default allowance of upload_max_filesize for uploaded files:

upload_max_filesize = 2M

You can find more information about this configuration at http://php.net/upload-max-filesize.

2 MB maximum for uploaded files can be considered small for PHP apps. So, go ahead and change it to suit your needs, as follows:

upload_max_filesize = 32M

The following are some other important lines/PHP directives to consider:

post_max_size = 48M
memory_limit = 256M
max_execution_time = 600

You can find more information about the preceding PHP directives and other directives for configuring your PHP at https://www.php.net/manual/en/ini.core.php.

  1. Restart Apache for the aforementioned modified PHP settings to take effect:
$ sudo service apache2 restart

PHP 7.4 is powerful. You can just have it installed and serving your site for development without relying on the Apache server if you don't want to install Apache on your local development machine. In the next section, you'll learn how to use PHP 7.4 without the Apache server.

Running PHP apps with a built-in PHP web server

Since PHP 5.4, you can run PHP scripts and apps with the built-in PHP web server, without needing a common web server such as Apache or Nginx. As long as you have PHP 7.4 installed, you can skip the preceding Apache installation. To start the PHP server, just open a terminal from your project's root directory and run the following command:

$ php -S 0.0.0.0:8181

If you want to start the app from a specific document root directory, such as from the public directory, in the project directory called www, do the following:

$ cd ~/www
$ php -S localhost:8181 -t public

Let's create a classic "Hello World" example that will be served up by this built-in PHP web server to see whether everything is set up correctly:

  1. Create a simple "Hello World" message page in a PHP file, as follows:
// public/index.php
<?php
echo 'Hello world!';
  1. Navigate to your project directory and start it with the built-in PHP web server by using the preceding command. The terminal should show the following information:
[Sun Mar 22 09:12:37 2020] PHP 7.4.4 Development Server (http://localhost:8181) started
  1. Now, load localhost:8181 on your browser. You should see Hello world! on your screen, without any errors.
If you want to learn about this built-in web server, visit https://www.php.net/features.commandline.webserver.

Next, you'll learn how to arm yourself with some PHP standards. You'll also understand what HTTP messages are and why we need PSR for modern PHP apps.

Understanding HTTP messages and PSRs

Hypertext Transfer Protocol (HTTP) is a communication protocol between client computers and web servers. A web browser such as Chrome, Safari, or Firefox can be the web client or the user-agent, while a web application on a computer that's listening on some port can be the web server. Web clients are not only browsers but any application that can speak to the web server, such as cURL or Telnet.

A client opens a connection via the internet to make a request to the server and waits until they receive a response from the server. The request contains request information, while the response contains status information and the requested content. These two types of exchanged data are called HTTP messages. They are just bodies of text encoded in ASCII and they span multiple lines in the following structure:

Start-line
HTTP Headers

Body

This looks very simple and straightforward, doesn't it? Although this may be the case, let's elaborate on this structure:

  • Start-line describes the implemented request method (such as GET, PUT, or POST), the request target (usually a URI), and the HTTP version or the status (such as 200, 404, or 500) of the response and the HTTP version. Start-line is always a single line.
  • The HTTP Headers line describes the specific details (meta-information) of the request or the response, such as Host, User-Agent, Server, Content-type, and so on.
  • The blank line indicates that all the meta-information for the request has been sent.
  • Body (or, message body) contains the exchanged data of the request (such as the content of an HTML form) or the response (such as the content of an HTML document). The message body is optional (sometimes, it is not needed in the request for requesting data from the server).

Now, let's use cURL to see how the data of the HTTP request and response is exchanged:

  1. Serve the PHP "Hello World" app that you learned about in the previous section on localhost:8181 using the built-in PHP web server:
$ php -S localhost:8181 -t public
  1. Open a new tab on your terminal and run the following cURL script:
$ curl http://0.0.0.0:8181 
--trace-ascii
/dev/stdout

You should see that the request message is displayed in the first part, as follows:

== Info: Trying 0.0.0.0:8181...
== Info: TCP_NODELAY set
== Info: Connected to 0.0.0.0 (127.0.0.1) port 8181 (0)
=> Send header, 76 bytes (0x4c)
0000: GET / HTTP/1.1
0010: Host: 0.0.0.0:8181
0024: User-Agent: curl/7.65.3
003d: Accept: /
004a:

Here, you can see that the blank line is represented at 004a: and that there is no message body in the request at all. The response message is displayed in the second part, as follows:

== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 20 bytes (0x14)
0000: Host: 0.0.0.0:8181
<= Recv header, 37 bytes (0x25)
0000: Date: Sat, 21 Mar 2020 20:33:09 GMT
<= Recv header, 19 bytes (0x13)
0000: Connection: close
<= Recv header, 25 bytes (0x19)
0000: X-Powered-By: PHP/7.4.4
<= Recv header, 40 bytes (0x28)
0000: Content-type: text/html; charset=UTF-8
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 12 bytes (0xc)
0000: Hello world!
== Info: Closing connection 0

Here, you can see the status is 200 OK in the start line in the response. But in the preceding example, we did not send any data, so there is no message body in the request message. Let's create another very basic PHP script, as follows:

  1. Create a PHP page with the PHP print function so that it displays POST data, as follows:
// public/index.php
<?php
print_r($_POST);
  1. Serve the page on localhost:8181 using the built-in PHP web server:
$ php -S localhost:8181 -t public
  1. Send some data over cURL on your terminal:
$ curl http://0.0.0.0:8181 
-d "param1=value1&param2=value2"
--trace-ascii
/dev/stdout

This time, the request message will be displayed in the first part, along with the message body:

== Info: Trying 0.0.0.0:8181...
== Info: TCP_NODELAY set
== Info: Connected to 0.0.0.0 (127.0.0.1) port 8181 (0)
=> Send header, 146 bytes (0x92)
0000: POST / HTTP/1.1
0011: Host: 0.0.0.0:8181
0025: User-Agent: curl/7.65.3
003e: Accept: /
004b: Content-Length: 27
005f: Content-Type: application/x-www-form-urlencoded
0090:
=> Send data, 27 bytes (0x1b)
0000: param1=value1&param2=value2
== Info: upload completely sent off: 27 out of 27 bytes

The response message is displayed in the second part, as follows:

== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 20 bytes (0x14)
0000: Host: 0.0.0.0:8181
<= Recv header, 37 bytes (0x25)
0000: Date: Sat, 21 Mar 2020 20:43:06 GMT
<= Recv header, 19 bytes (0x13)
0000: Connection: close
<= Recv header, 25 bytes (0x19)
0000: X-Powered-By: PHP/7.4.4
<= Recv header, 40 bytes (0x28)
0000: Content-type: text/html; charset=UTF-8
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 56 bytes (0x38)
0000: Array.(. [param1] => value1. [param2] => value2.).
Array
(
[param1] => value1
[param2] => value2
)
== Info: Closing connection 0
  1. Here, you can also see the request message and the request message for the PUT method over cURL on your terminal:
$ curl -X PUT http://0.0.0.0:8181 
-d "param1=value1&param2=value2"
--trace-ascii
/dev/stdout
  1. The same applies for the DELETE method over cURL, as follows:
$ curl -X DELETE http://0.0.0.0:8181 
-d "param1=value1&param2=value2"
--trace-ascii
/dev/stdout
  1. Last but not least, we also can use the Developer Tools in Google Chrome to help us inspect the exchanged data. Let's create another simple PHP script that will receive data from the URI:
// public/index.php
<?php
print_r($_GET);
  1. Send some data on your browser by using 0.0.0.0:8181/?param1=value1&param2=value2. By doing this, the data is sent as param1=value1&param2=value2, as shown in the following screenshot:

If you want to know more about HTTP and HTTP messages, please visit https://developer.mozilla.org/en-US/docs/Web/HTTP for HTTP in general and https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages for HTTP messages specifically.

When it comes to server-side development, HTTP messages are better encapsulated in objects so that they are easier to work with. For example, Node.js has a built-in HTTP module (https://nodejs.dev/the-nodejs-http-module) for HTTP communication, in which you can get the HTTP messages objects from the callback in the http.createServer() method when using it to create an HTTP server:

const http = require('http')

http.createServer((request, response) => {
response.writeHead(200, {'Content-Type': 'text/plain'})
response.end('Hello World')
}).listen(8080)

If you are using a Node.js framework such as Koa, you can find the HTTP messages objects in ctx, as follows:

const Koa = require('koa')
const app = new Koa()

app.use(async ctx => {
ctx
ctx.request
ctx.response
})

In the preceding code, ctx is the Koa context, while ctx.request is the HTTP request message and ctx.response is the HTTP response message. We can do the same in Express; you can find the HTTP messages as follows:

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

Unlike Node.js, PHP has never had built-in HTTP message objects like these. There are a bunch of ways to get and set the web data, both manually and directly, just like we saw in the previous PHP examples, by using superglobals ($_GET, $_POST) and the built-in functions (echo, print_r). If you want to catch the incoming request, you can use $_GET, $_POST, $_FILE, $_COOKIE, $_SESSION, or any other superglobals (https://www.php.net/manual/en/language.variables.superglobals.php), depending on the situation.

The same goes for returning a response: you use global functions such as echo, print, and header to set response headers manually. In the past, PHP developers and frameworks had their own ways of implementing HTTP messages. This led to a time where different frameworks had different abstractions to represent HTTP messages, and any app based on a specific implementation of HTTP messages could hardly be interoperable in the project for using other frameworks. This lack of industry standards made the components of a framework tightly coupled. If you didn't start with a framework, you would end up building one yourself.

But today, the PHP community has learned and enforced PHP standards and recommendations. You don't have to fully comply with these standards and recommendations; you can ignore them if you have philosophical reasons that urge you to. But they are a measure with a good intention to end the PHP war at least commercially and collaboratively. And once for all, PHP developers can focus on PHP standards rather than frameworks in a framework-agnostic way. When we talk about PHP standards, we tend to refer to the PSR, a PHP specification defined and published by the PHP Framework Interop Group (PHP-FIG). PSR-7: HTTP message interfaces is one of the specifications suggested by the PHP-FIG members and was voted for according to the established protocol that they agreed upon.

PSR-7 was officially accepted in May 2015. It is basically used to standardize HTTP message interfaces. Before jumping into PSR-7, we should know about some other PSR numbers as well, notably PSR-12 (replacement of PSR-2), PSR-4, and PSR-15. We will guide you through them in this book so that you can write reusable, framework-agnostic apps and components that can be used on their own or can be interoperable with other frameworks, whether they are a full-stack or micro frameworks. Let's get started!

Why PSRs?

Internally, PHP never tells developers how they should write their PHP code. For example, Python uses indentation to indicate a block of code, while for other programming languages such as PHP and JavaScript, indentation in code is done for readability. The following is an example of what Python will accept:

age = 20
if age == 20:
print("age is 20")

Python will return an error if there's no indentation:

if age == 20:
print("age is 20")

The number of spaces is up to the coder's preference, but you must use at least one and have the same number of spaces for other lines in the same block; otherwise, Python will return an error:

if age == 20:
print("age is 20")
print("age is 20")

On the other hand, in PHP, you can write the following:

if (age == 20) {
print("age is 20");
}

The following is valid in PHP too:

if (age == 20) {
print("age is 20");
print("age is 20");
}

Python internally enforces readability and tidiness for code. PHP does not. You can imagine that without some basic enforcement, and depending on the experience of the coder, PHP code can end up very messy, ugly, and unreadable. Perhaps the low barrier to entry in PHP web development plays a part in that. So, your PHP code must adhere to a common code style to make it easy for collaboration and maintenance.

There are a few PHP coding standards around for specific frameworks, but they are more or less based on (or similar to) PSR standards:

Pragmatically, your code should adhere to the framework that you are tied to, and that specific framework only. But if you are only using some components or libraries from the framework, then you can comply with any combination of PSRs, or the coding standards made by PEAR. The PEAR Coding Standards can be found at https://pear.php.net/manual/en/standards.php.

This book focuses on a variety of PSRs because this chapter aims to create framework-agnostic PHP apps. You don't have to agree with PSR, but if you are looking for a standard to start a project with and do not have any standard of your own within your organization, it might be a good place to start. You can find out more about the PSR at https://www.php-fig.org/psr/.

On top of what we have mentioned here, you should also check out PHP: The Right Way at https://phptherightway.com/. It outlines things that a modern PHP coder can use as references, from setting up PHP, dependency management with Composer (which we will cover later in this chapter), coding style guides (in which PSRs are recommended), dependency injection, databases, templating, to testing frameworks and more. It is a good start for new PHP coders who want to avoid the mistakes of the past and find links to authoritative PHP tutorials on the web. It is also a good place for experienced PHP coders who need a quick reference and updates from the PHP community at large, or anything they might have missed in the past few years.

Now, let's dive into PSRs, starting with PSR-12.

PSR-12 – Extended Coding Style guide

PSR-12 is a revised coding style guide of PSR-2 that takes PHP 7 into account. The PSR-12 specification was approved on 9 August 2019. Since PSR-2 was accepted in 2012, many changes have been made to PHP that have had some impact on coding style guidelines, the most notable of which is return type declarations, which are introduced in PHP 7 and not described in PSR-2. Hence, a standard should be defined for using them so that they can be adopted by the wider PHP community before individual PHP coders implement their standards, which might conflict with each other eventually.

For example, the return type declarations that have been added in PHP 7 simply specifies the type of value that a function should return. Let's take a look at the following function, which adopts the return type declarations:

declare(strict_types = 1);

function returnInt(int $value): int
{
return $value;
}

print(returnInt(2));

You will get the correct result of 2 as an integer. However, let's see what happens if you change the code inside the returnInt function, as follows:

function returnInt(int $value): int
{
return $value + 1.0;
}

PHP will give up with the following error:

PHP Fatal error: Uncaught TypeError: Return value of returnInt() must be of the type int, float returned in ...

So, to cater for the need of this new feature of PHP 7, PSR-12 requires you to use a single space after the colon, followed by the type declaration for the methods with the return type declaration. Also, the colon and declaration must be on the same line as the argument list's closing parenthesis with no spaces between the two characters. Let's take a look at a simple example that has a return type declaration:

class Fruit
{
public function setName(int $arg1, $arg2): string
{
return 'kiwi';
}
}

Some rules are kept the same in PSR-2 and PSR-12. For example, in both PSRs, you must not use tabs for indentation but four single spaces instead. But the rule in PSR-2 regarding the list of blocks has been revised. Now, in PSR-12, the blocks that use statements for importing classes, functions, and constants must be separated by a single blank line, even though there is just one import of them. Let's take a quick look at some code that complies with this rule:

<?php

/**
* The block of comments...
*/

declare(strict_types=1);

namespace VendorNamePackageName;

use VendorNamePackageName{ClassX as X, ClassY, ClassZ as Z};
use VendorNamePackageNameSomeNamespaceClassW as W;

use function VendorNamePackageName{functionX, functionY, functionZ};

use const VendorNamePackageName{ConstantX, ConstantY, ConstantZ};

/**
* The block of comments...
*/
class Fruit
{
//...
}

Now, you should notice that, in PSR-12, you must use a single blank line right after the opening <?php tag. However, in PSR-2, this isn't necessary. For example, you can write the following:

<?php
namespace VendorNamePackageName;

use FruitClass;
use VegetableClass as Veg;

It is worth knowing that PSR-2 was extended from PSR-1, which was a basic coding standard, but since PSR-12 was accepted, PSR-2 is now officially deprecated.

To implement these PSRs for your code, please visit the following sites:

If you want to find out the new features in PHP 7, such as scalar type declarations and return type declarations, please visit https://www.php.net/manual/en/migration70.new-features.php.

PSR-12 helps PHP coders write more readable and structured code, so it is worth adopting it in your code when writing in PHP. Now, let's move on to PSR-4, which allows us to use autoloading in PHP.

PSR-4 – Autoloader

In the old days of PHP, if you wanted to bring a third-party library into your PHP project, or bring in your functions and classes from separate PHP files, you would use include or require statements. With the arrival of PHP autoloading, you would use the __autoload magic method (which is now deprecated since PHP 7.2) or spl_autoload to automatically call your code. Then came true namespace support in PHP 5.3, where developers and frameworks can devise their approaches to prevent naming collisions. But still, it was quite far from ideal because of the battle between different approaches. You can imagine a situation where you have two frameworks – framework A and framework B  and individual developers disagreeing with each other and implementing their own ways to achieve the same result. This was madness.

Today, we comply with PSR-4 (which is the successor of PSR-0) to standardize the autoloading approach and bind developers and frameworks together. It specifies the standard for autoloading classes from file paths. It also describes the location of the file. So, a fully qualified class name should adhere to the following form:

<NamespaceName>(<SubNamespaceNames>)<ClassName>

In this rule, we have the following:

  • The namespace for a fully qualified class must have a top-level vendor namespace, which is the <NamespaceName> part in the preceding code.
  • You can use one or more sub-namespaces, as shown by the <SubNamespaceNames> part of the preceding code.
  • Then, you must end the namespace with your class name, as shown by the <ClassName> part of the preceding code.

So, if you are writing an autoloader, it is recommended to use this standard. However, you don't have to (and probably shouldn't) go through the hassle of writing your own autoloader while complying with PSR-4. This is because you can use Composer to help you do this. Composer is a package manager for PHP. It is akin to npm in Node.js. It was initially released in 2012. Since then, it has been used by all modern PHP frameworks and PHP coders alike. This means you can focus more on your code development and worry less about the interoperability of different packages and libraries that you are going to bring into your project environment.

Before we start, make sure you have Composer installed on your system. Depending on your system, you can follow the following guides to install Composer:

The current version is 1.10.9. Follow these steps to install Composer and make use of the autoloader that it offers:

  1. Install Composer in your current directory by running the following script in your terminal:
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === 'e5325b19b381bfd88ce90a5ddb7823406b2a38cff6bb704b0acc289a09c8128d4a8ce2bbafcd1fcbdc38666422fe2806') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
  1. Run the Composer setup file, as follows:
$ sudo php composer-setup.php

You should get the following output in your terminal:

All settings correct for using Composer
Downloading...

Composer (version 1.10.9) successfully installed to: /home/lau/composer.phar
Use it: php composer.phar
  1. Remove the Composer setup file, as follows:
$ php -r "unlink('composer-setup.php');"
  1. Verify the installation by running php composer.phar on your terminal. If you want to use Composer globally, then move Composer to /usr/local/bin (if you are using Linux/Unix):
$ sudo mv composer.phar /usr/local/bin/composer
  1. Now, you can run Composer globally. To verify it, just run the following command:
$ composer

You should see the logo for Composer, as well as its available commands and options:

   ______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ / __ __ / __ / __ / ___/ _ / ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 1.10.9 2020-07-16 12:57:00
...
...

Alternatively, you can use the -V option to check the version you've installed directly:

$ composer -V
Composer version 1.10.9 2020-07-16 12:57:00
  1. Now that you have Composer installed on your system, simply navigate to your project's root through your terminal and use composer require, followed by <package-name>, to install any third-party packages (also known as dependencies) that you need in your project, as follows:
$ composer require monolog/monolog
  1. After installing the required packages, you can go to your project root. You should see that a composer.json file has been created that contains the dependency of your project in a require key:
{
"require": {
"monolog/monolog": "^2.0"
}
}
  1. If you want to install all the dependencies again next time, you can just run the install command, as follows:
$ composer install
  1. When you have installed your project dependencies, whether that be with the require or install command, you will always get a /vendor/ folder generated by Composer that contains all your dependencies. An autoload.php file will always be generated and located inside the /vendor/ folder. You then can include this file and start using the classes that those packages provide straight away, as follows:
require __DIR__ . '/vendor/autoload.php';

$log = new MonologLogger('name');
$log->pushHandler(new MonologHandlerStreamHandler('path/to/your.log', MonologLogger::WARNING));
$log->addWarning('Foo');
$log->error('Bar');
  1. Most importantly, you can even add your classes to the autoloader by adding the autoload key, along with your custom namespace, to the composer.json file. For example, you can have your classes stored in an /src/ folder in the project root, on the same level where the /vendor/ directory is:
{
"autoload": {
"psr-4": {"Spectre\": "src/"}
}
}

If you have the source files in multiple locations, you can use an array, [], to associate it with your custom namespace, as follows:

{
"autoload": {
"psr-4": {
"Spectre\": [
"module1/",
"module2/"
]
}
}
}

Composer will register a PSR-4 autoloader for the Spectre namespace. After that, you can start writing your classes. For example, you can create a /src/Foo.php file that contains a SpectreFoo class. After that, just run dump-autoload on your terminal to regenerate the autoload.php file inside the /vendor/ directory. You also can add multiple custom namespaces to the autoload field, as follows:

{
"autoload": {
"psr-4": {
"Spectre\": [
//...
],
"AnotherNamespace\": [
//...
]
}
}
}

Besides PSR-4, Composer also supports PSR-0. You can add a PSR-0 key to the composer.json file.

For more information and examples of how to use PSR-0 with Composer, please visit https://getcomposer.org/doc/04-schema.mdautoload. However, please note that PSR-0 is now deprecated. If you want to read more about these two PSRs, please visit https://www.php-fig.org/psr/psr-0/ for PSR 0 (deprecated) and https://www.php-fig.org/psr/psr-4/ for PSR-4.

If you want to know about Monolog, which we used in the preceding example for logging in PHP, please visit https://github.com/Seldaek/monolog. If you want to read more about Autoloading Classes in PHP, please visit https://www.php.net/manual/en/language.oop5.autoload.php.

As soon as you have armed yourself with knowledge about PSR-12 and PSR-4, it will be easier for you to build PHP apps that comply with other PSRs. The other two PSRs that this book focuses on are PSR-7 and PSR-15. Let's move on and look at PSR-7 first.

PSR-7 – HTTP Message Interfaces

Earlier, we mentioned that PHP does not have HTTP request and response message objects, which is why PHP frameworks and coders came out with different abstractions to represent (or "imitate") HTTP messages in the past. Luckily, in 2015, PSR-7 came to the rescue to end these "disagreements" and differences.

PSR-7 is a set of common interfaces (abstractions) that specify public methods for HTTP messages and URIs when communicating over HTTP. In object-oriented programming (OOP), an interface is, in fact, an abstraction of the actions (public methods) that an object (a class) must implement, without defining the complexities and details of how these actions are implemented. For example, the following table shows the methods that your HTTP message classes must implement when composing them so that they comply with the PSR-7 specification.

The specified methods for accessing and modifying the request and response objects are as follows:

 To access  To modify
getProtocolVersion() withProtocolVersion($version)
getHeaders() withHeader($name, $value)
hasHeader($name) withAddedHeader($name, $value)

getHeader($name)

getHeaderLine($name)

withoutHeader($name)
getBody() withBody(StreamInterface $body)

The specified methods for accessing and modifying just request objects are as follows:

To access To modify
  • getRequestTarget()
  • getMethod()
  • getUri()
  • getServerParams()
  • getCookieParams()
  • getQueryParams()
  • getUploadedFiles()
  • getParsedBody()
  • getAttributes()
  • getAttribute($name, $default = null)
  • withMethod($method)
  • withRequestTarget($requestTarget)
  • withUri(UriInterface $uri, $preserveHost = false)
  • withCookieParams(array $cookies)
  • withQueryParams(array $query)
  • withUploadedFiles(array $uploadedFiles)
  • withParsedBody($data)
  • withAttribute($name, $value)
  • withoutAttribute($name)


The specified methods for accessing and modifying just response objects are as follows:

To access To modify
  • getStatusCode()
  • getReasonPhrase()
  • withStatus($code, $reasonPhrase = '')


Since PSR-7 was accepted on 18 May 2015, many packages have been made based on it. You can develop your own version as long as you implement the interfaces and methods specified in PSR-7. However, you might be "reinventing the wheel" since there are PSR-7 HTTP messages packages out there already – unless you have some strong reasons to do so. So, for the sake of getting it started quickly, let's use zend-diactoros from Zend Framework. We will "reuse" the PSR knowledge (PSR-12 and PSR-4) you gained in the previous sections to create a simple "Hello World" server-side app with HTTP messages. Let's get started:

  1. Create a /public/ directory in the app root with an index.php file in it. Add the following lines to it to bootstrap the app environment:
// public/index.php
chdir(dirname(__DIR__));
require_once 'vendor/autoload.php';

In these two lines of code, we have changed the current directory from /path/to/public to /path/to so that we can import the autoload.php file by writing vendor/autoload.php instead of ../vendor/autoload.php.

The __DIR__ (magic) constant is used to get the directory path of the current file, which is index.php, in the /path/to/public/ directory. The dirname function is then used to get the parent directory's path, which is /path/to.  The chdir function is then used to change the current directory. 

Note that in the upcoming sections on PSRs, we will use this pattern to bootstrap the app environment and import the autoload file. Please visit the following links to find out more about the constants and functions mentioned previously:

Also, note that you must run all the incoming PHP apps on a terminal by using the built-in PHP web server, as follows:

$ php -S localhost:8181 -t public
  1. Install zend-diactoros into the app's root directory via Composer:
$ composer require zendframework/zend-diactoros
  1. To marshal an incoming request, you should create a request object in the index.php file in the /public/ directory, as follows:
$request = ZendDiactorosServerRequestFactory::fromGlobals(
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
  1. Now, we can create a response object and manipulate the response, as follows:
$response = new ZendDiactorosResponse();
$response->getBody()->write("Hello ");
  1. Note that the write method is specified in the stream interface (StreamInterface) and that we can also append more data by making multiple calls with this method:
$response->getBody()->write("World!");
  1. We can then manipulate the headers if needed:
$response = $response
->withHeader('Content-Type', 'text/plain');
  1. Note that the headers should be added after data is written to the body. Then, that's it – you have managed to transform the simple PHP "Hello World" app that you learned about at the beginning of this chapter into a modern PHP app with PSR-7! However, if you run this PSR-7 "Hello World" app on your browser with php -S localhost:8181 -t public from your terminal, you will see nothing on the screen. This is because we did not emit the response to the browser with PSR-15 HTTP Server Request Handlers and PSR-7 HTTP Response Emitter, which we will cover in the next section. If you want to see the output, for now, you can access the data by using the getBody method and then using echo:
echo $response->getBody();
  1. If you inspect the Content-type of your page through the Developer Tools on Chrome, you'll get text/html instead of text/plain, which is what we modified with the withHeader method. We will get the correct content type with the emitter in the next chapter.
For more information about zend-diactoros and their advanced usage, please visit https://docs.zendframework.com/zend-diactoros/. Besides zend-diactoros from Zend Framework, you also can use the HTTP messages package from other frameworks and libraries:

You should check out the PSR-7 documentation at https://www.php-fig.org/psr/psr-7/ for more information about this PSR. If you are new to PHP interfaces, please visit https://www.php.net/manual/en/language.oop5.interfaces.php for further reading.

From the PSR-7 documentation, you can find out the rest of the public methods that are not mentioned in this book. They should be expected in any PSR-7 HTTP messages package, such as zend-diactoros. It is useful to know about these methods so you know what you can do with them. You also can use the built-in PHP get_class_methods method at runtime to list all the methods that you can use in the request and response objects. For example, for the request object, you can do the following:

$request = ZendDiactorosServerRequestFactory::fromGlobals(
//...
);
print_r(get_class_methods($request));

You will get a list of request methods in an array that you can call. The same goes for the response object; you will get a list of response methods in an array by doing this:

$response = new ZendDiactorosResponse();
print_r(get_class_methods($response));

Now, let's move on and look at PSR-15, where we'll find out how to emit the response to the client (browser).

PSR-15 – HTTP Server Request Handlers (request handlers)

PSR-7 was a great step in the PHP community, but it is only halfway to the goal that could free PHP coders from monolithic MVC frameworks and allow them to compose agnostic PHP apps out of a range of reusable middlewares. It only defines HTTP messages (the request and the response); it never defines how to deal with them afterward. So, we need a request handler to process the request in order to produce a response.

Like PSR-7, PSR-15 is a set of common interfaces, but they take things a step further and specify the standard for request handlers (HTTP server request handlers) and middleware (HTTP server request middleware). It was accepted on 22 January 2018. We will cover the HTTP server request middleware in the next section. Now, let's understand the HTTP server request handlers in the PSR-15 interface, RequestHandlerInterface:

// PsrHttpServerRequestHandlerInterface

namespace PsrHttpServer;

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;


interface RequestHandlerInterface
{
public function handle(ServerRequestInterface $request) :
ResponseInterface;
}

As you can see, this is a very simple interface. It only has one specified public method, handle, which only accepts a PSR-7 HTTP request message and must return a PSR-7 HTTP response message. We will use the zend-httphandlerrunner component from Zend Framework, which implements this interface, to provide utilities that we can use to emit PSR-7 responses. Let's get it hooked up to the app:

  1. Install zend-httphandlerrunner via Composer:
$ composer require zendframework/zend-httphandlerrunner
  1. As soon as we have it installed in our project environment, we can send the response that we created previously to the browser, as follows:
//...
$response = $response
->withHeader('Content-Type', 'text/plain');

(new ZendHttpHandlerRunnerEmitterSapiEmitter)->emit($response);

If you inspect Content-Type of your page again through the Developer Tools on Chrome, you'll get the correct content type, which is text/plain.

For more information about zend-httphandlerrunner, visit https://docs.zendframework.com/zend-httphandlerrunner/. For more information about PSR-15, visit https://www.php-fig.org/psr/psr-15/.

Besides zend-httphandlerrunner, you can also use Http Response Emitter from Narrowspark at https://github.com/narrowspark/http-emitter to handle the request and emit the response. Now, let's move on and look at the second interface of PSR-15: MiddlewareInterface.

PSR-15 – HTTP Server Request Handlers (middleware)

The middleware interface in PSR-15 has the following abstraction:

// PsrHttpServerMiddlewareInterface

namespace PsrHttpServer;

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;

interface MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
) : ResponseInterface;
}

Again, you can see it is a very simple interface. It only has one specified public method process for middleware implementation. The component (middleware) that implements this interface will only accept a PSR-7 HTTP request message and a PSR-15 HTTP server request handler and then must return a PSR-7 HTTP response message.

We will use the zend-stratigility components from Zend Framework, which implement this interface, to allow us to create PSR-15 middleware in our app. Let's learn how to get it hooked up to the app:

  1. Install zend-stratigility via Composer:
$ composer require zendframework/zend-stratigility
  1. As soon as we have it installed in our project environment, we will import the middleware function and MiddlewarePipe class, as follows:
use function ZendStratigilitymiddleware;

$app = new ZendStratigilityMiddlewarePipe();

// Create a request
$request = ZendDiactorosServerRequestFactory::fromGlobals(
//...
);
  1. Then, we can use this middleware function to create three middlewares and attach them to the pipeline, as follows:
$app->pipe(middleware(function ($request, $handler) {
$response = $handler->handle($request);
return $response
->withHeader('Content-Type', 'text/plain');
}));

$app->pipe(middleware(function ($request, $handler) {
$response = $handler->handle($request);
$response->getBody()->write("User Agent: " .
$request->getHeader('user-agent')[0]);
return $response;
}));

$app->pipe(middleware(function ($request, $handler) {
$response = new ZendDiactorosResponse();
$response->getBody()->write("Hello world! ");
$response->getBody()->write("Request method: " .
$request->getMethod() . " ");
return $response;
}));
  1. As you can see, the "Hello World" code block we created previously now is a piece of middleware that's stacked with other middleware. Finally, we can generate a final response from these pieces of middleware and emit it to the browser, as follows:
$response = $app->handle($request);
(new ZendHttpHandlerRunnerEmitterSapiEmitter)->
emit($response);

You should get a result similar to the following on your browser at 0.0.0.0:8181:

Hello world!
Request method: GET
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
For more information about zend-stratigility, visit https://docs.zendframework.com/zend-stratigility/.

Besides zend-stratigility, you can also use the following packages to create your middleware:

So, there you go. With the help of several interoperable components, we have bootstrapped a modern PHP app that complies with PSR-12, PSR-7, and PSR-15, which means you can freely pick (agnostically) from a broad range of vendor implementations of those standards for your HTTP messages, request handler, and middleware. But we haven't finished yet. As you may have noticed, the app we have created is just a one-page app that runs on a single "route" at 0.0.0.0:8181. It doesn't have any other routes, such as /about, /contact, and so on. Therefore, we need a router that implements PSR-15. We'll cover this in the next section.

PSR-7/PSR-15 router

We will use Route from The League of Extraordinary Packages (a PHP developer group) so that we have a PSR-7 routing system and dispatch our PSR-15 middleware on it. In short, Route is a fast PSR-7 routing/dispatcher package.

It is a PSR-15 server request handler and can handle the invocation of a stack of middleware. It is built on top of FastRoute (https://github.com/nikic/FastRoute) by Nikita Popov.

Let's learn how to get it hooked up to the app:

  1. Install league/route via Composer:
$ composer require league/route
  1. Once you have it installed, we can refactor our "Hello World" component with a route, as follows:
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

$request = ZendDiactorosServerRequestFactory::fromGlobals(
//...
);

$router = new LeagueRouteRouter;

$router->map('GET', '/', function (ServerRequestInterface $request) : ResponseInterface {
$response = new ZendDiactorosResponse;
$response->getBody()->write('<h1>Hello, World!</h1>');
return $response;
});
  1. Then, we just have to create a PSR-7 HTTP response by using a dispatch method from Route and emitting it to the browser:
$response = $router->dispatch($request);
(new ZendHttpHandlerRunnerEmitterSapiEmitter)->emit($response);

Check out the list of HTTP request methods (get, post, put, delete, and so on) that you can use at https://route.thephpleague.com/4.x/route. What's more, we can attach middleware to our app.

  1. If you want to lock down the entire app, you can add the middleware to the router, as follows:
use function ZendStratigilitymiddleware;

$router = new LeagueRouteRouter;
$router->middleware(<middleware>);
  1. If you want to lock down a group of routes, you can add the middleware to the group, as follows:
$router
->group('/private', function ($router) {
// ... add routes
})
->middleware(<middleware>)
;
  1. If you want to lock down a specific route, you can add the middleware to that route, as follows:
$router
->map('GET', '/secret', <SomeController>)
->middleware(<middleware>)
;
  1. For example, you can use Route with zend-stratigility:
use function ZendStratigilitymiddleware;

$router = new LeagueRouteRouter;
$router->middleware(middleware(function ($request, $handler) {
//...
}));
  1. If you don't want to use the middleware function or prefer not to use zend-stratigility at all, you can create anonymous middleware, as follows:
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;

$router = new LeagueRouteRouter;

$router->middleware(new class implements MiddlewareInterface {
public function process(ServerRequestInterface $request,
RequestHandlerInterface $handler) : ResponseInterface
{
$response = $handler->handle($request);
return $response->withHeader('X-Clacks-Overhead',
'GNU Terry Pratchett');
}
});

As long as you comply with PSR7 and PSR-15 by implementing the process method in your middleware, there's no need for zend-stratigility at all. If you want to create class-based middleware in a separate PHP file, please check out the example provided at https://route.thephpleague.com/4.x/middleware/.

For more information about Route from The League of Extraordinary Packages, visit https://route.thephpleague.com/. You can also check out other packages that have been created by this group of developers at https://thephpleague.com/. Besides Route from The League of Extraordinary, you can also use the following packages for HTTP routers based on PSR-7 and PSR-15:

You may need a dispatcher to use with some of these packages. The advantage of using Route from The League of Extraordinary Packages is that it provides a router and a dispatcher in one package.

With that, we have composed an agnostic PHP app by using PSR-12, PSR-4, PSR-7, and PSR-15. But our PHP API isn't done yet. There's one more task to do – we need to add a database framework for CRUD operations. We will guide you through this task in the next section.

Writing CRUD operations with PHP database frameworks

As you may recall from Chapter 9, Adding a Server-Side Database, CRUD stands for create, read, update, and delete. In that chapter, we used MongoDB to create CRUD operations. In this section, we will use MySQL to create backend authentication. We will use MySQL with PHP in the PHP app we have just created with PSRs. So, let's start by creating the table that we will need in the MySQL database.

Creating MySQL tables

Make sure you have installed MySQL Server on your local machine and created a database called nuxt-php. Once you've done that, follow these steps to finish up the first part of our API:

  1. Insert the following SQL query to create the table in the database:
CREATE TABLE user (
uuid varchar(255) NOT NULL,
name varchar(255) NOT NULL,
slug varchar(255) NOT NULL,
created_on int(10) unsigned NOT NULL,
updated_on int(10) unsigned NOT NULL,
UNIQUE KEY slug (slug)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

The first thing you will have noticed is that we are using uuid instead of id like we did in Chapter 12Creating User Logins and API Authentication. UUID stands for Universally Unique Identifier. There may be reasons why and benefits that will make you want to choose UUIDs over auto increment keys for indexing records in database tables. For example, you can create a UUID without connecting to the database. It is practically unique across apps, so you can easily combine data from different databases and never get a clash. To generate UUIDs in a PHP app, we can use ramsey/uuid by Ben Ramsey to help us generate RFC 4122 (https://tools.ietf.org/html/rfc4122) version 1, 3, 4, and 5 UUIDs.

  1. So, let's install ramsey/uuid via Composer:
$ composer require ramsey/uuid
  1. Now, you can use this package to generate version 1 of UUID, as follows:
use RamseyUuidUuid;

$uuid1 = Uuid::uuid1();
echo $uuid1->toString();
If you want to find out more information about this package, visit https://github.com/ramsey/uuid.

Now, let's learn how to use PHP to work with MySQL databases and find out why we need a database framework to speed up our development in PHP.

Using Medoo as a database framework

In the old days of PHP, developers used MySQL functions (https://www.php.net/manual/en/ref.mysql.php) to manage MySQL databases. Then, the MySQLi extension (https://www.php.net/manual/en/book.mysqli.php) came to replace MySQL functions, which are now deprecated. However, now, developers are encouraged to use PHP Data Objects (PDO) (https://www.php.net/manual/en/book.pdo.php). PDO is a built-in PHP interface abstraction, just like PSR-7 and PSR-15 are. It is a data-access abstraction layer that provides a consistent interface (a unified API) for accessing and managing databases (for example, MySQL and PostgreSQL), which means regardless of which database you are using, you use the same functions to query and fetch data. It supports the following databases:

  • CUBRID
  • MS SQL Server
  • Firebird
  • IBM
  • Informix
  • MySQL
  • Oracle
  • ODBC and DB2
  • PostgreSQL
  • SQLite
  • 4D


Note that PDO is a data-access abstraction layer, not a database abstraction layer. Hence, depending on which database you use, a PDO driver for that database must be installed for using PDO. We are using a MySQL database, so we must make sure that the PDO_MYSQL driver is installed. In Ubuntu, you can use the following command to check whether you have the PDO extension is enabled and that PDO_MYSQL driver is installed in your environment:

$ php -m

You should get a list of PHP modules. Look for PDO and pdo_mysql:

[PHP Modules]
...
PDO
pdo_mysql
...

Another more specific option you can use to check for PDO and its drivers is as follows:

$ php -m|grep -i pdo
PDO
pdo_mysql

If you just want to search for PDO drivers, do the following:

$ php -m|grep -i pdo_
pdo_mysql

You can also create a PHP page with phpinfo() to look for them. Alternatively, you can use the getAvailableDrivers method, as follows:

print_r(PDO::getAvailableDrivers());

You should get a list of PDO drivers, as follows:

Array
(
[0] => mysql
)

Alternatively, there are some built-in PHP functions that can help you:

extension_loaded ('PDO'); // returns boolean
extension_loaded('pdo_mysql'); // returns boolean
get_loaded_extensions(); // returns array

If you don't see any PDO drivers, then you must install the driver for MySQL support. Follow these steps to do so:

  1. Search for the package name (Ubuntu):
$ apt-cache search php7.4|grep mysql
php7.4-mysql - MySQL module for PHP
  1. Install php7.4-mysql and restart your Apache server:
$ sudo apt-get install php7.4-mysql
$ sudo service apache2 restart

Once you have the PDO_MYSQL driver in place, you can start writing CRUD operations immediately. For example, let's write an insert operation, as follows:

  1. Create the MySQL database connection:
$servername = "localhost";
$username = "<username>";
$password = "<password>";
$dbname = "<dbname>";
$connection = new PDO(
"mysql:host=$servername;dbname=$dbname",
$username,
$password
)
Note that <username>, <password>, and <dbname> are placeholders for the actual connection details. You must change them according to your own database settings.
  1. Prepare the SQL query and bind parameters:
$stmt = $connection->prepare("
INSERT INTO user (
uuid,
name,
slug,
created_on,
updated_on
) VALUES (
:uuid,
:name,
:slug,
:created_on,
:updated_on
)
");
$stmt->bindParam(':uuid', $uuid);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':slug', $slug);
$stmt->bindParam(':created_on', $createdOn);
$stmt->bindParam(':updated_on', $updatedOn);
  1. Insert a fresh row:
$uuid = "25769c6c-d34d-4bfe-ba98-e0ee856f3e7a";
$name = "John Doe";
$slug = "john-doe";
$createdOn = (new DateTime())->getTimestamp();
$updatedOn = $createdOn;
$stmt->execute();

This is not ideal because you have to prepare the statement every time and bind parameters where they needed, and this takes quite a lot of lines to operate. For this reason, we should pick a PHP database framework to accelerate development. Medoo (https://medoo.in/) is one of the choices out there. It is lightweight and very easy to integrate and use.

Let's get it installed and hooked up to our app:

  1. Install Medoo via Composer:
$ composer require catfan/medoo
  1. If everything is set and in place, you can import Medoo and pass in an array of configuration to start the database connection, just as we did previously in the vanilla approach:
use MedooMedoo;

$database = new Medoo([
'database_type' => 'mysql',
'database_name' => '<dbname>',
'server' => 'localhost',
'username' => '<username>',
'password' => '<password>'
]);

That's it for establishing a connection to the MySQL database through this database framework. You can find the actual usage of this snippet in /chapter-16/nuxt-php/proxy/backend/core/mysql.php in this book's GitHub repository. We will show you how to implement it in the upcoming section, but for now, let's explore how to write some basic CRUD operations with Medoo.

Inserting records

You can use the insert method when you want to insert new records into a table, as follows:

$database->insert('user', [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507',
'name' => 'Jane',
'slug' => 'jane',
'created_on' => '1568072289'
]);
If you want to find out more details about this method, visit https://medoo.in/api/insert.

Querying records

You can use the select method when you want to list records from a table, as follows:

$database->select('user', [
'uuid',
'name',
'slug',
'created_on',
'updated_on',
]);

The select method gives you a list of records. If you just want to select a specific row, you can use the get method, as follows:

$database->get('user', [
'uuid',
'name',
'slug',
'created_on',
'updated_on',
], [
'slug' => 'jane'
]);
If you want to find out more details, visit https://medoo.in/api/select for the select method and https://medoo.in/api/get for the get method.

Updating records

You can use the update method when you want to modify the data of a record in a table, as follows:

$database->update('user', [
'name' => 'Janey',
'slug' => 'jane',
'updated_on' => '1568091701'
], [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);
If you want to find out more details about this method, visit https://medoo.in/api/update.

Deleting records

You can use the delete method when you want to remove a record from a table, as follows:

$database->delete('user', [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);

If you want to find out more details about this method, visit https://medoo.in/api/delete.

That's it regarding how to write basic CRUD operations with Medoo and PDO.

Please check out Medoo's documentation at https://medoo.in/doc for the rest of the methods that you can use. There are other alternatives to Medoo, such as Doctrine DBAL at https://github.com/doctrine/dbal and Eloquent at https://github.com/illuminate/database.

In this section, you studied a handful of PSRs and CRUD operations. Next, we will cover how to put all these together and integrate them with Nuxt. Since PHP and JavaScript are two different languages, the only way for them to talk to each other is through JSON in the API.

But before we write a script that will enable that, we should look into the cross-domain application structure for these two programs.We have been using cross-domain application structures for our Nuxt apps since Chapter 12, Creating User Logins and API Authentication, so this should be familiar to you. Let's get started!

Structuring cross-domain app directories

Again, just like when structuring cross-domain app directories, the following is our holistic view for Nuxt and our PHP API:

// Nuxt app
front-end
├── package.json
├── nuxt.config.js
└── pages
├── index.vue
└── ...

// PHP API
backend
├── composer.json
├── vendor
│ └── ...
├── ...
└── ...

Individually, the directory structure for Nuxt remains the same. We only have to make a slight change to the API directory's structure, as follows:

// PHP API
backend
├── composer.json
├── middlewares.php
├── routes.php
├── vendor
│ └── ...
├── public
│ └── index.php
├── static
│ └── ...
├── config
│ └── ...
├── core
│ └── ...
├── middleware
│ └── ...
└── module
└── ...

The directory structure for the PHP API is a suggestion. You can always design a structure that you prefer and that suits you the most. So, at a glance, we have the following:

  • The /vendor/ directory is where all the third-party packages or dependencies are kept.
  • The /public/ directory only contains an index.php file that initiates our API.
  • The /static/ directory for static files, such as a favicon.
  • The /config/ directory stores the configuration files, such as MySQL files.
  • The /core/ directory stores the common objects and functions that we can use across the app.
  • The /middleware/ directory stores our PSR-15 middleware.
  • The /module/ directory stores the custom modules we will create later, just as we did in Chapter 12Creating User Logins and API Authentication, with Koa.
  • The composer.json file is always located at the root level.
  • The middlewares.php file is the core location for importing middleware from the /middleware/ directory.
  • The routes.php file is the core location for importing routes from the /module/ directory.

Once you have the structure ready, you can start writing the top-level code that will glue the other code from the different locations and directory into a single app in the index.php file in the /public/ directory. So, let's get started:

  1. Put the foreach loop in the routes.php file to iterate each module that you will create later:
// backend/routes.php
$modules = require './config/routes.php';

foreach ($modules as $module) {
require './module/' . $module . 'index.php';
}
  1. Create a routes.php file in the /config/ directory that will list the filename of your module, as follows:
// backend/config/routes.php
return [
'Home/',
'User/'.
//...
];
  1. In this PHP API, the middlewares.php file will import a piece of middleware that is used to decorate the CRUD operation's output:
// backend/middlewares.php
require './middleware/outputDecorator.php';

This decorator will print the CRUD operation's output in JSON in the following format:

{"status":<status code>,"data":<data>}
  1. Create a file called outputDecorator.php in the /middleware/ directory that contains the following code. This will wrap the operation's output in the preceding format:
// backend/middleware/outputDecorator.php
use function ZendStratigilitymiddleware;

$router->middleware(middleware(function ($request, $handler) {
$response = $handler->handle($request);
$existingContent = (string) $response->getBody();
$contentDecoded = json_decode($existingContent, true);
$status = $response->getStatusCode();
$data = [
"status" => $status,
"data" => $contentDecoded
];
$payload = json_encode($data);

$response->getBody()->rewind();
$response->getBody()->write($payload);

return $response
->withHeader('Content-Type', 'application/json')
->withStatus($status);
}));

Here, we use the middleware method from the zend-stratigility component to create the decorator middleware. Then, we lock down the entire application with this middleware by using the router from league/route by The League of Extraordinary.

  1. Create a mysql.php file in the /core/ directory that returns the Medoo instance for the MySQL connection:
// backend/core/mysql.php
$dbconfig = require './config/mysql.php';
$mysql = new MedooMedoo([
'database_type' => $dbconfig['type'],
'database_name' => $dbconfig['name'],
'server' => $dbconfig['host'],
'username' => $dbconfig['username'],
'password' => $dbconfig['password']
]);
return $mysql;
  1. As we mentioned earlier, the /public/ directory only contains an index.php file. This is used to initiate our program, so it contains the script that you learned about previously regarding PSRs:
// backend/public/index.php
chdir(dirname(__DIR__));
require_once 'vendor/autoload.php';

$request = ZendDiactorosServerRequestFactory::fromGlobals(
//...
);

$router = new LeagueRouteRouter;
try {
require 'middlewares.php';
require 'routes.php';
$response = $router->dispatch($request);
} catch(Exception $exception) {
// handle errors
}

(new ZendHttpHandlerRunnerEmitterSapiEmitter)->emit($response);

Here, you can see that the middlewares.php and routes.php files are imported into this file to produce a PSR-7 response. They are wrapped in the try and catch blocks to catch any HTTP errors, such as 404 and 506 errors. Due to this, any output from the module and any errors will be emitted to the browser through the last line. Hopefully, this has given you a bird's-eye view on this simple API. Now, let's move on and dive into the /module/ directory in more detail to learn how to create modules and routes.

Creating the API's public routes and their modules

Creating the API's public routes and their modules is very similar to the API you learned to build in the previous chapters of this book; the main difference is the language. We used JavaScript and the Node.js framework – Koa, previously, while for the API in this chapter, we are using PHP and PSRs to create a framework-agnostic API. So, let's get started:

  1. Create two directories in the /module/ directory: one called Home and another called User. These two sub-directories are the modules in this API. In each module, create a /_routes/ directory and an index.php file that will import the routes from the /_routes/ directory, as follows:

└── module
├── Home
│ ├── index.php
│ └── _routes
│ └── hello_world.php
└── User
├── index.php
└── _routes
└── ...
  1. In the Home module, output a "Hello world!" message and map it to the / route, as follows:
// module/Home/_routes/hello_world.php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

$router->get('/', function (ServerRequestInterface $request) :
ResponseInterface {
return new ZendDiactorosResponseJsonResponse(
'Hello world!');
});
  1. In the User module, write the CRUD operations so that we can create, read, update, and delete our users. So, in the /_routes/ directory, create five files called fetch_user.php, fetch_users.php, insert_user.php,  and update_user.php, and delete_user.php. In each of these files, we will map the route for each of the CRUD operations in the /Controller/ directory:
└── User
├── index.php
├── _routes
│ ├── delete_user.php
│ ├── fetch_user.php
│ └── ...
└── Controller
└── ...
  1. For example, in the fetch_users.php file, we will define a /users route for listing all the users, as follows:
// module/User/_routes/fetch_users.php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;

$router->get('/users', function (ServerRequestInterface $request) : ResponseInterface {
$database = require './core/mysql.php';
$users = (new SpectreUserController
FetchUsers($database))->fetch();
return new ZendDiactorosResponseJsonResponse($users);
});

Here, you can see that we import the Medoo instance as $database and pass it the controller that will perform the Read operation and then call the fetch method to get all the available users.

  1. So, the next thing we will do is create some CRUD directories: Insert, Fetch, Update, and Delete. In each of these CRUD directories, we will store the PSR-4 classes inside the /Controller/ directory, as follows:
└── Controller
├── Controller.php
├── Insert
│ └── User.php
├── Fetch
│ ├── User.php
│ └── Users.php
├── Update
│ └── User.php
└── Delete
└── User.php
  1. First of all, create an abstract class that can be extended by the classes in the CRUD directories. This class will only accept the MedooMedoo database in its constructor, as follows:
// module/User/Controller/Controller.php
namespace SpectreUserController;

use MedooMedoo;

abstract class Controller
{
protected $database;

public function __construct(Medoo $database)
{
$this->database = $database;
}
}
  1. Import the preceding abstract class and extend it to any other classes that need to connect to the MySQL database, as follows:
// module/User/Controller/Fetch/Users.php
namespace SpectreUserControllerFetch;

use SpectreUserControllerController;

class Users extends Controller
{
public function fetch()
{
$columns = [
'uuid',
'name',
'slug',
'created_on',
'updated_on',
];
return $this->database->select('user', $columns);
}
}

In this class, we use the select method to fetch all the users from the user table in the MySQL database. Medoo will return an Array containing the list of users or an empty Array if there are no users. This result will then be converted into JSON using the JsonResponse method from zend-diactoros in the fetch_users.php file.

Finally, it will be decorated by the middleware in the /middleware/ directory. This will result in the following output:

{"status":200,"data":[{"uuid":"...","name":"Jane","slug":"jane",...},{...},{...}]}

That's it regarding the PHP API. It's pretty easy, isn't it? In this exercise, we will skip the task of dealing with CORS on the API side as we will be using the Nuxt Axios and Proxy modules to handle CORS seamlessly and effortlessly for us in the Nuxt app that we are going to create. So, let's get started!

You can find this PHP API in /chapter-16/nuxt-php/proxy/backend/ and the rest of the CRUD classes of this API in /chapter-16/nuxt-php/proxy/backend/module/User/Controller/ in this book's GitHub repository.

Integrating with Nuxt

The @nuxtjs/axios module is well integrated with the @nuxtjs/proxy module and is very useful in many cases. Preventing the CORS problem is one of the benefits of using these two modules together. You learned how to install and use them in Chapter 6, Writing Plugins and Modules. Let's recap:

  1. Install the @nuxtjs/axios and @nuxtjs/proxy modules via npm:
$ npm install @nuxtjs/axios
$ npm install @nuxtjs/proxy
  1. Register @nuxtjs/axios in the modules option in the Nuxt config file, as follows:
// nuxt.config.js
module.exports = {
modules: [
'@nuxtjs/axios'
],

axios: {
proxy: true
},

proxy: {
'/api/': { target: 'http://0.0.0.0:8181',
pathRewrite: {'^/api/': ''} }
}
}

Note that it is not required to register the @nuxtjs/proxy module when you are using it with @nuxtjs/axios, as long as it is installed and in the dependencies field in package.json.

In the preceding configuration, we use /api/ as the proxy for http://0.0.0.0:8181, which is where our PHP API is running. So, whenever we use /api/ in any of our API endpoint requests, it is calling 0.0.0.0:8181. For example, let's say you are making an API call, as follows:

$axios.get('/api/users')

The @nuxtjs/axios and @nuxtjs/proxy modules will convert that /api/users endpoint into the following:

http://0.0.0.0:8181/api/users

But since we don't use /api/ in our PHP API's routes, we use pathRewrite in the configuration to remove it during the call. Then, the actual URL that's sent by the @nuxtjs/axios and @nuxtjs/proxy modules to the API is as follows:

http://0.0.0.0:8181/users
Once more, visit the following links for more information about these two modules:

After the installation and configuration are in place, we can start creating the frontend UI for communicating with the PHP API. We'll look at this in the next section.

Creating CRUD pages

Again, this is not a completely new task to you as this is almost the same as creating the CRUD pages you learned to create in Chapter 9, Adding a Server-Side Database. Let's recap:

  1. Create the following pages in the /pages/users/ directory for sending and fetching data:
users
├── index.vue
├── _slug.vue
├── add
│ └── index.vue
├── update
│ └── _slug.vue
└── delete
└── _slug.vue
  1. For example, use the following script to fetch all the available users:
// pages/users/index.vue
export default {
async asyncData ({ error, $axios }) {
try {
let { data } = await $axios.get('/api/users')
return {
users: data.data
}
} catch (err) {
// handle errors.
}
}
}

The scripts, templates, and directory structure in this Nuxt app are the same as the app you learned to create in Chapter 9, Adding a Server-Side Database. The difference is that an _id was used in that chapter but in this chapter, we're using _slug. By now, you should be able to complete the rest of the CRUD pages on your own. However, you can always revisit the following sections in Chapter 9, Adding a Server-Side Database, for more information:

  • Creating an Add page
  • Creating an Update page
  • Creating a Delete page

Once you have created these pages, you can run the Nuxt app with npm run dev. You should see the app running on your browser at localhost:3000.

You can find the complete source code for this app in /chapter-16/nuxt-php/proxy/frontend/nuxt-universal/ in this book's GitHub repository.

If you don't want to use the @nuxtjs/axios and @nuxtjs/proxy modules in this Nuxt app, you can find the complete source regarding how to enable CORS in the PHP API for the Nuxt app in /chapter-16/nuxt-php/cors/ in this book's GitHub repository.

You can also find a copy of the database saved as user.sql in /chapter-16/nuxt-php/ in this book's GitHub repository.

Now, let's summarize what you've learned in this long chapter. We hope you have enjoyed this chapter and found it inspiring.

Summary

In this chapter, you have not only managed to decouple a Nuxt app from an API, similar to what you did in Chapter 12Creating User Logins and API Authentication, but you also managed to write an API in a different language, PHP, one of the most popular server-side scripting languages in web development. You learned how to install PHP and Apache in order to run PHP apps or use the built-in PHP web server for development, all while complying with PSR-12, PSR4, PSR7, and PSR-15 in order to build a modern framework-agnostic app. You also learned to use the PHP database framework known as Medoo for writing CRUD operations, reusing the Nuxt app from Chapter 9Adding a Server-Side Database, but with a few modifications, and gluing the frontend UI and the backend API together perfectly. Now, you also understand HTTP messages in more detail and know how to use PDO for modern PHP database management. Well done.

In the next chapter, you will discover what else you can do with Nuxt in terms of real-time apps. Here, you will learn about Socket.io and RethinkDB. We will walk you through the installation process for these two technologies. Then, you will learn how to perform real-time CRUD operations in a RethinkDB database, write real-time code in JavaScript with Socket.io, and integrate them with the Nuxt app. This will be another interesting and exciting chapter that we will guide you through. So, stay tuned!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.227.69