PHP Routing & Restful API

In this tutorial, we are going to create restful APIs with PHP. The APIs will be accessed by Angular app to fetch products, add, update and delete products from Mysql database.
Now you have to install Composer on your local development machine. Composer is a very helpful tool to manage dependencies in PHP. Here is where you can download Composer: https://getcomposer.org/. Again, if you haven't installed NPM, VSC editor, and XAMPP, visit the Angular & PHP  for download links.
In the xampp/htdocs folder of XAMPP, create a folder named mysite. In the mysite folder, create another folder named api. In the api folder, create composer.json file.

{
    "name": "php_api",
    "description": "PHP APIs",
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
    
}

This configuration allows you to use App name while namespacing the files instead of app directory. For example, you can specify a namespace like App\Controllers.
Now, the project structure looks like this.

mysite
     -api
          -composer.json

Then in the api folder, execute the following command to initialize Composer. You will see vendor folder and autoload.php file in it.

composer dump-autoload

In this tutorial, we are going to use symfony to make route system in PHP. Thus, you have to install two packages from symfony: symfony/routing and symfony/http-foundation by executing the following commands:

composer require symfony/routing
composer require symfony/http-foundation

If you check inside the vendor folder, you can see that a new folder called Symfony has been created.
Now, we start creat routing sytem in PHP using Symfony. In the api folder, create four folders: app, config, db, and public folders. So, our project looks like this.

mysite
     -api
          -composer.json
          -app
          -config
          -db
          -public
                
In the config folder, create config.php with the following content:
<?php

//App Root
define('APP_ROOT', dirname(dirname(__FILE__)));
define('URL_ROOT', '/mysite');
// sub folder
define('URL_SUBFOLDER', '/api');

In the routes folder, create web.php file. The web.php file defines routes for our api. For now, we simply has one route to home page. We will add more routes later.
<?php 

use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

// Routes collection
$routes = new RouteCollection();
// add route to the collection
$routes->add('homepage', new Route(constant('URL_SUBFOLDER') . '/', array('controller' => 'HomeController', 'method'=>'indexAction'), array()));

The add() method of the RouteCollection take two arguments. The first argument is the route name and the second argument is a Route object in which you define route path and array of custom attributes that you want to return when this particular route is matched. Typically, the array would be a combination of the controller and method which you would like to call when this route is requested.
In the app folder, create Router.php file. The Router.php checks if the routes defined in routes/web.php exist and calls method of the right controller.
<?php 

namespace App;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\NoConfigurationException;

class Router
{
    public function __invoke(RouteCollection $routes)
    {
        $context = new RequestContext();
        $request = Request::createFromGlobals();
        $context->fromRequest(Request::createFromGlobals());

        // Routing can match routes with incoming requests
        $matcher = new UrlMatcher($routes, $context);
        try {
            $matcher = $matcher->match(substr($_SERVER['REQUEST_URI'],strlen(URL_ROOT)));
			
    
            // Cast params to int if numeric
            array_walk($matcher, function(&$param)
            {
                if(is_numeric($param)) 
                {
                    $param = (int) $param;
                }
            });
    
            $className = '\\App\\Controllers\\' . $matcher['controller'];
            $classInstance = new $className();
    
            // Add routes as paramaters to the next class
            $params = array_merge(array_slice($matcher, 2, -1), array('routes' => $routes));
            // call method of the right controller    
            call_user_func_array(array($classInstance, $matcher['method']), $params);
            
        } catch (MethodNotAllowedException $e) {
            echo 'Route method is not allowed.';
        } catch (ResourceNotFoundException $e) {
           echo 'Route does not exists.'.$e;
        } catch (NoConfigurationException $e) {
            echo 'Configuration does not exists.';
       }
    }
}

// Invoke
$router = new Router();
$router($routes);


In the app folder, create Controllers folder. Inside the Controllers folder, create HomeController.php file. It simply show "Home Page" message when you access the root of the api.
<?php 

namespace App\Controllers;

use App\Models\Product;
use Symfony\Component\Routing\RouteCollection;

class HomeController
{
        // Homepage action
	public function indexAction(RouteCollection $routes)
	{
		
		echo 'Home Page';
	}
}

Now, include the routes engine in the public/index.php file (in public folder) in:
<?php

// autoload
require_once '../vendor/autoload.php';
// Load Config
require_once '../config/config.php';
// Routes
require_once '../routes/web.php';
require_once '../app/Router.php';

?>

The index.php file is the entry point of our APIs. To tell Apache to redirect all request to the entry point, We create .htaccess file in the api folder and add the following configuration:
RewriteEngine On

# Stop processing if already in the /public directory
RewriteRule ^public/ - [L]

# Static resources if they exist
RewriteCond %{DOCUMENT_ROOT}/public/$1 -f
RewriteRule (.+) public/$1 [L]

# Route all other requests
RewriteRule (.*) public/index.php?route=$1 [L,QSA]

# Set the headers for the restful api
Header always set Access-Control-Allow-Origin *
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"

In the configuration, we also specify CORS - CROSS-ORIGIN REQUEST HEADERS to allow APIs being accessed from client app (e.g Angular app).
Now start Apache. Then go to web browser and access http://localhost/mysite/api/. You would get "Home Page" message.

Comments

Popular posts from this blog

Angular with PHP & MYSQL to display products list & paging

Angular Upload Form Data & Files

PHP Mysql Database Migration Using Phinx