Single point of entry

The principle of a single entry point is very simple.

The web server is configured so that all HTTP requests, regardless of their URL, are processed by the same index.php script .HTTP requests are sent to the server where they are redirected to index.phpRedirecting all requests to index.php

The current URL can be obtained from the $ _SERVER [‘REQUEST_URI’] variable . The next step is to write your own rules for processing URLs. A simplified example:

<?php
if($_SERVER['REQUEST_URI'] === '/about')  
   echo 'About the site';
elseif ($ _ SERVER ['REQUEST_URI'] === '/ contacts')
    echo 'Contacts';
else
    echo 'Error 404';

However, there is one omission in the above diagram. After all, if the server receives a request for an existing file (style.css, script.js, logo.png, etc.), the server must send this file, and not redirect it.Handling requests using a single entry pointHow a single entry point works

That’s the whole principle of a single entry point. This is how it works in popular CMS like WordPress and Opencart, in Laravel frameworks, Symfony, etc.

The only question that remains for you to decide is what to do with requests to existing folders.

I personally prefer to redirect them to index.php as well.

In fact, websites often use 2 entry points.

The first is index.php, the second is a separate script designed to work with the site through the console.

The pros of a single point of entry

  • Allows the use of CNC
  • Allows complete URL management in PHP, including storing URLs in a database
  • Scripts with configs, important functions and libraries are connected only once and become available everywhere. There is no need to duplicate their connection anywhere else.

Single entry point with Apache

To configure a single entry point, you need to add several lines to the web server config. The easiest way to do this is with the .htaccess file .

This file allows you to override Apache settings for specific sites and folders.

Add the following settings to .htaccess:


# Enable redirection
RewriteEngine On
# Do not apply to existing files files
RewriteCond% {REQUEST_FILENAME}! -F
# Do not apply to existing directories
RewriteCond% {REQUEST_FILENAME}! -D
# Redirect all requests to index.php
# L stands for Last, so mod_rewrite stops working immediately at this stage.
# In short, a slight increase in performance.
RewriteRule. * Index.php [L]

To make the redirect work for existing directories, remove the line with ! -D at the end, like this:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php [L]

Done. You can get the URL of the current page from the $ _SERVER [‘REQUEST_URI’] variable .

Also on the Internet you can often find another version of the config, it differs only in the last line:

RewriteRule ^(.*)$ index.php?url_param=$1 [L,QSA]

The main difference is that the URL of the current page will be stored both in $ _SERVER [‘REQUEST_URI’] and in a separate GET parameter, in our case $ _GET [‘url_param’] , and this URL will be cleared of GET- parameters.

The QSA flag is needed because without it the GET parameters won’t work, i.e. the $ _GET array will only contain url_param and nothing else.

Which of the two options to choose is up to you, personally I like the first one more.

Single entry point with Nginx

Open the domain config and write the following rule inside the server section:

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

Simple routing

If the single entry point is configured correctly, then when visiting any non-existent URL, for example / test , the index.php file should run.

The URL of the current page is in the $ _SERVER variable [‘REQUEST_URI’]

<?php
var_dump($_SERVER['REQUEST_URI']); // /test

Now we can write a very simple router that looks at the current URL and connects the appropriate script:

<?php
$uri = $_SERVER['REQUEST_URI'];

if($uri === '/')
    require 'pages/main.php';
elseif($uri === '/about')
    require 'pages/about.php';
else
    require 'pages/error404.php';

Let’s make a couple more improvements. First, URLs often need to work regardless of the presence of GET parameters, so we strip them from the URI:

// /
about? id = 5 becomes / about
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

In addition, you often need to access a specific part of the URL. To do this, let’s split the URL into parts with a slash:

$segments = explode('/', trim($uri, '/'));

The $ segments variable for the URL / products / 15 will contain an array like [0 => ‘products’, 1 => ’15’] .

Now we can easily add routes for the admin panel:

<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$segments = explode('/', trim($uri, '/'));

if($segments[0] === 'admin')
{
    if($segments[1] === 'users')
        $file = 'admin_users.php';
    elseif($segments[1] === 'products')
        $file = 'admin_products.php';
    else
        $file = 'admin_404.php';
}
else
{
    if($uri === '/')
        $file = 'main.php';
    elseif($uri === '/about')
        $file = 'about.php';
    else
        $file = '404.php';
}

require 'pages/' . $file;

This is the simplest routing option. Not ideal, of course, but also not requiring knowledge of regular expressions (although no one bothers to use them) and connecting third-party libraries.

When storing URLs in a database, the routing will look something like this (the actual code depends on the library you are using to interact with the database):

<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($uri, '/'));

$current_page = $db->select('SELECT * FROM `pages` WHERE `url` = ?', [$uri])->first();

// If there is no article - show an error
if (! $ current_page)
    require 'pages / 404.php';
// If there is an article - connect the template in which the variable $ current_page will be available
else
    require 'pages / article.php';

Htaccess routing

Some time ago it was popular to write routing rules directly in htaccess, here are some examples:

RewriteRule ^/product-(.*)_([0-9]+).php /redirectold.php?productid=$2

RewriteRule ^products/([^/]+)/([^/]+)/([^/<WBR>]+) product.php?category=$1&brand=$2&<WBR>product=$3

RewriteRule ^news/20[0-9]{2}/[0-9]{2}/[0-9]{2}/[^/]+\.html index.php

RewriteCond %{DOCUMENT_ROOT}/name/$1.php -f
RewriteRule ^([^/]+)/([^/]+)/?$ $.php1?action=$2 [L,NC,QSA]

RewriteCond %{DOCUMENT_ROOT}/name/$1.php -f
RewriteRule ^([^/]+)/([^/]+)/([^/]+)/?$ $1.php?action=$2&id=$3 [L,NC,QSA]

This approach has several disadvantages:

  • Poor readability of rules
  • You need to know the regulars well
  • Storing routing rules in your web server settings is conceptually not a good idea

In short, don’t use this approach.

Sick bastard meme - I like to configure routing in htaccess

URL structure in the admin area

Typically, URL addresses in the admin panel are formed according to one of the following schemes:


/ module / action / parameter1 / value1 / parameter2 / value2
/ module / action / value1 / value2

Let’s look at a simple example right away:


/ products - view the catalog
/ products / add - add a product
/ products / update - product update
/ products / delete - delete a product

So, we see that the module here is products , and the action, for example, is add . What to do with it now?

If you are familiar with OOP and MVC, then the module for you will be the name of the class, and the action is the method of this class that you want to run. If no action is specified, then it is customary to run a method called index.

If you don’t understand anything, take the module as the name of the file to be connected, and the action as, in fact, the action to be performed.

Let’s rewrite the example we wrote in the single entry point to the new URL scheme:

<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($uri, '/'));

$file = 'pages/' . $segments[0] . '.php';

if(file_exists($file))
    require $file;
else
    require 'pages/404.php';

So, we take the 1st URL fragment and check if a file with that name exists in the pages folder.

Those. when going to the page / test / test2, the script will check the existence of the file /pages/test.php . If the file exists, PHP will execute this file, otherwise the file /pages/404.php will be executed .

As you can see, with this approach, we no longer need to write a mapping between URLs and PHP files. PHP itself will search for the required file in the pages folder by the first fragment of the URL.

Now all that remains is to create the pages / products.php file . Let’s make a small blank:

<?php
if(empty($segments[1]))
{
    // 
Displaying the product catalog
}
elseif($segments[1] === 'add')
{
    // 
If the request came with the POST method
    if($_SERVER['REQUEST_METHOD'] === 'POST')
    {
        // add a new product to the database
    }
    // If the request came with the GET method
    else
    {
// display the form for adding a product
    }
}

This is how action processing looks like. We look at the second fragment of the URL and look for a handler for this action. For each action (add, update, delete), you need to register a separate elseif block.

Inside the add handler, we look at which method the request came in, GET or POST. If GET – display the form, if POST – add the product.

If you don’t like nested method validation, you can do it differently. In the index.php file, save the method into a separate variable:

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($uri, '/'));
$method = $_SERVER['REQUEST_METHOD'];
// ...

Then in products.php we change the template to the following:

<?php
if(empty($segments[1]) && $method === 'GET')
{
// Display the product catalog
}
elseif($segments[1] === 'add' && $method === 'GET')
{
// display the form for adding a product
}
elseif($segments[1] === 'add' && $method === 'POST')
{
    // add a new product to the database
}

Done. Yes, if you do not like the fact that the same action occurs 2 times in the code, only with different methods, you can use a slightly simplified URL scheme from the Laravel framework:


(GET) / products - display products
(GET) / products? Id = 5 - display of the 5th product page
(GET) / products / create - displaying the form for adding a product
(POST) / products / store - save product from the add form
(GET) / products / edit / 15 - displaying the form for editing a product with id = 15
(POST) / products / update - saving a product from the edit form
(POST) / products / destroy - deleting a product by its identifier in the database

Adding / admin / prefix to URL

Let’s change the index.php code a bit :

<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($uri, '/'));

if($segments[0] === 'admin')
{
    $file = 'pages/admin_' . $segments[1] . '.php';

    if(file_exists($file))
        require $file;
    else
        require 'pages/admin_404.php';
}
else
    require 'pages/404.php';

Now, when requesting the page / admin / products, PHP will look for a file called not products.php , but admin_products.php .

Rename the file and do not forget to replace all $ segments [1] in it with $ segments [2], since $ segments [1] now contains a module, and $ segments [2] has an action.

Advanced router FastRoute

If you’re looking for a more advanced routing system, I recommend checking out the FastRoute library . It is a very powerful router, perfect for complex applications, especially if you are using OOP.

If you want me to write a separate article on working with FastRoute – write about it in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *

Leave a Reply

Your email address will not be published. Required fields are marked *