After the successful implementation of Enplated MVC, you can start building your project.
The MVC architecture is one of the most popular software development architectures. Understanding it properly will make developing with Enplated MVC much easier.
MVC is an architectural pattern that separates an application into three main logical components: Model, View, and Controller. Each of them handles specific development aspects of an application. In Enplated MVC, they work as follows:
The following diagram illustrates this:
A demo blog app built with Enplated MVC is available in the latest release on GitHub. Just download the demo-vX-X.zip file.
.htaccess handles the redirection of all requests as follows:
index.php performs the following operations:
$_ENV['REQUEST'] = [
'URI' => "...", // for example: users/veronica/details
'METHOD' => "...", // for example: GET / POST...
];The .env file is located in the root of the application. It contains global variables, database connection details and other basic application settings.
The file must contain at least these 4 items:
If PRODUCTION is set to true (PRODUCTION = true), the application will run in production mode. In this mode, the application will not display any errors to the user, but will display a 500 error page instead. If it is set to false, the application will show the Built-in Debugger in case of an error.
The CONSOLE_LOG variable is used to display information about routing into the JS console. More information can be found in the JS Debugger section.
The DEBUG_BUFFER variable is used to allow the PHP eval function to dump files into the /debug folder. More information can be found in the PHP Eval Dump section.
The BASE_URL variable is used to set the base URL of the application. It is used to create links in the application. For example, if the application is running at the https://example.com URL, the BASE_URL variable should be set to BASE_URL = 'https://example.com'. If the application is running at the http://localhost/projects/myapp/test URL, the BASE_URL variable should be set to http://localhost/projects/myapp/test.
You can add any other variables to the .env file. The values are then accessible in the application via the $_ENV['APP']['variable'] superglobal variable or via the getAppEnvVar('variable') helper function. We will add some more for the database connection in the Database Connection section.
Routing and Controllers are a very important part of any MVC framework. First, let's clarify a few terms:
For example, we can have 2 Routes Files: one contains Routes for GET /homepage, GET /about and GET /contact, the other contains Routes GET /api/login, POST /api/login and GET /api/logout. Each Route has its own Controller.
Create a new PHP file in the /routes folder. The file must not be named default. You can also create a file in a folder nested in /routes. This creates our Routes File. Now we need to register it. Go to /routes/default.php and add this command at the beginning of the file:
registerRoutesFile(__DIR__ . '/nameOfRouteFile.php');For example, if we created a Routes File named users.php, we would do this:
registerRoutesFile(__DIR__ . '/users.php');If users.php is in the management folder (/routes/management/users.php), then we modify the command as follows:
registerRoutesFile(__DIR__ . '/management/users.php');Go to the Routes File created in the previous section. Call a function with 3 parameters:
checkRoute(METHOD, PATH, function() {...});
For example, if we want to create a simple route for the /contact path, we would write this:
checkRoute('GET', '/contact', function() {
... // Controller
});
In the Controller, we can use any PHP code. We should get data from the Model, process it and pass it to the View. More information can be found in the Models and Views sections.
Routes can also contain parameters. Parameters are variables that are passed in the path. For example, if we have a path /users/veronica/details, we can extract veronica from the path.
To create a route with parameters, we need to use the {param} syntax. For example, if we want to create a route for the /users/{username}/details path, we would write this:
checkRoute('GET', '/users/{username}/details', function() {
... // Controller
});
We can then get the username parameter value from the path in the Controller with the getRequestParam('name') function. For example:
$username = getRequestParam('username');
checkRoute('GET', '/users/{username}/details', function() {
/*
This Router is called when the path is /users/_anything_/details.
If the path is /users/veronica/details, the $username variable will contain 'veronica'.
If the path is /users/thomas/details, the $username variable will contain 'thomas'.
*/
$username = getRequestParam('username');
});
More complex paths can be created by combining static parts of the path with parameters. For example, if we want to create a route for the /users/{username}/details/{id} path, we would write this:
checkRoute('GET', '/users/{username}/details/{id}', function() {
/*
This Router is called when the path is /users/_anything_/details/_anything_.
*/
$username = getRequestParam('username');
$id = getRequestParam('id');
});
If you want some code to run on every request, you can put it in the /index.php file. For example, if you want to start a session on every request, you can call the session_start() function at the beginning of the /index.php file.
The /public folder is the only folder that is accessible from the outside. This is where all the static files (images, CSS, JS, etc.) are stored. Do not place any PHP files in this folder and do not place any images, CSS or JS files in any other folder. How to access these files is described in the Views section.
If you want to exclude a folder from routing, you can do so by adding the folder name to the .htaccess file. For example, if you want to exclude the /cache folder from routing, you would change the .htaccess file as follows:
RewriteEngine on
# serve existing files in the /public folder directly
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^public/(.*)$ public/$1 [L]
# added line
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^cache/(.*)$ cache/$1 [L]
# otherwise, route the request to index.php
RewriteRule ^ index.php
Database connection is a very important part of any application. Enplated MVC supports connections to MySQL, MariaDB and SQLite databases.
Navigate to the .env file and add the following variables:
DB_HOST = 'host' ; address of the database, for example localhost
DB_USER = 'user' ; username for the database
DB_PASS = 'pass' ; password for the database
DB_NAME = 'db_name' ; name of the database
DB_PORT = 3306 ; port of the database, for example 3306
Then navigate to the /engine/database/setup.php file and add the following code at the top of the file:
connectToMysqli("db", "DB_HOST", "DB_USER", "DB_PASS", "DB_NAME", "DB_PORT");
db is the name of the database connection. You can have multiple connections to different databases. The connection name must be unique. After a successful connection, you can get it with the getDatabaseEnvConn('db') function. More information can be found in the Get Database Connector section.
The other values are names of variables from the .env file. The names can be changed - for example, you can use HOST_DB_1 instead of DB_HOST.
Navigate to the /engine/database/setup.php file and add the following code at the top of the file:
connectToSqlite("db", __DIR__ . "/db.sqlite");
In a Controller, you can get the Database Connector with the getDatabaseEnvConn('name') function. For example, if we have a connection named db, we would write this:
$db = getDatabaseEnvConn('db');
With a MySQL / MariaDB connection, we get a mysqli object. With an SQLite connection, we get a PDO object.
We can then use the $db object to execute queries. More information can be found in the Models section.
Enplated MVC supports only MySQL, MariaDB and SQLite databases. If you want to use another database type, you need to create a new function in the /engine/database/setup.php file. You can use the connectToMysqli / connectToSqlite functions as examples.
Models are another important part of any MVC framework. They provide a partial abstraction for Controllers: the Controller calls a Model Function, and the code in that function is responsible for returning the data. In OOP terms, a Model is roughly a class and a Model Function is a method.
Models are created in the /models folder. The file name must be the same as the name of the Model. For example, if we want to create a Model for the users table, we would create a file named users.php.
You do not need to register Models anywhere (as you do with Routes Files). The Model is registered automatically when the Controller calls it.
Model Functions are pure functions. They do not affect anything outside of themselves, apart from changing the state of the DB, etc. The Controller typically calls one or more functions from the Model(s), and they return data to the Controller.
For example, let's create two functions for the users model: one returns the user ID by username, the other returns the user's details. We would write this in the models/users.php file:
<?php
function getUserId($db, $username) {
$sql = "SELECT id FROM users WHERE username = ?";
$stmt = $db->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
if ($result->num_rows == 0) {
return null;
}
return $result->fetch_assoc()["id"];
}
function getUserDetails($db, $id) {
$sql = "SELECT * FROM users WHERE id = ?";
$stmt = $db->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
if ($result->num_rows == 0) {
return null;
}
return $result->fetch_assoc();
}
?>
Now go to the Routes File and call the Model Functions from a Controller. We can call a Model Function with the modelCall("model_name", "function_name", [params..]) function.
For example, if we want to create a Controller and call the users model for the /users/{username}/details path, we would write this:
checkRoute('GET', '/users/{username}/details', function() {
$username = getRequestParam('username');
$db = getDatabaseEnvConn('db');
// ┌ model name ┌ first parameter ┌ second parameter
// │ │ │
$userId = modelCall("users", "getUserId", ["db" => getDatabaseEnvConn("db"), "username" => $username]);
// │
// └ function name in model
...
});
Now you should be able to create a route, call the Model function and get the data from the Model function. How to display the data is described in the Views section.
Views are the last part of the MVC framework. They are responsible for displaying the data to the user. The View is generally HTML, supplemented with special tags for outputting data or perhaps smaller sections of PHP code.
All View files must be located in the /views root folder (or subfolders). They must end with the .enp extension. After the file is created, you can write any HTML code or special syntax similar to PHP (you can learn more about it in the Syntax section).
If you are using Visual Studio Code, you can install the Enplated MVC Extension. This extension provides syntax highlighting for the .enp files.
If you still do not see the syntax highlighting, open any .enp file and click on the bottom right corner where it says Plain Text. Select Enplated MVC Blade from the list.
You can also click Configure File Association for .enp and select Enplated MVC Blade.
Enplated MVC View (Blade) files use a syntax similar to Laravel Blade. It is very simple and easy to learn. The following special tags are available:
@include('path')
Includes another .enp file. The path is relative to the /views folder. For example, if we want to include the /views/partials/header.enp file, we would write this:
@include('partials/header')
{{ $variable }}
Outputs the value of the variable. For example, if we want to output the $username variable, we would write this:
<p>User {{ $username }} is registered from {{ $date }}.</p>
How to pass the variable to the View is described in the Calling a View Renderer section.
{{ env('value') }}
Outputs the value of a variable from the .env file. For example, if we want to output the BASE_URL variable, we would write this:
<a href="{{ env('BASE_URL') }}/contact/veronica">Contact Veronica</a>
{{-- comment --}}
Comments out the code. This section will not be rendered in the final HTML.
{{-- This code is horrible and should be rewritten, but do not show it to the user. --}}
@php, @endphp
Starts a block of PHP code. You can write any PHP code between the @php and @endphp tags. For example, if we want to get the current date, we would write this:
@php
$date = date("Y-m-d");
@endphp
@if, @elseif, @else, @endif
Starts a conditional statement. You can write any code between the @if and @endif tags. For example, if we want to print a badge based on the user's status, we would write this:
@if ($privilegeLevel == 'admin')
<span class="badge badge-danger">Admin</span>
@elseif ($privilegeLevel == 'user')
<span class="badge badge-primary">User</span>
@else
<span class="badge badge-secondary">Guest</span>
@endif
@foreach, @endforeach
Starts a loop. You can write any code between the @foreach and @endforeach tags. For example, if we want to print all users, we would write this:
@foreach ($users as $user)
<li>{{ $user['name'] }}</li>
@endforeach
@for, @endfor
Starts a loop. You can write any code between the @for and @endfor tags. For example, if we want to print numbers from 1 to 10, we would write this:
@for ($i = 1; $i <= 10; $i++)
Number: {{ $i }}
@endfor
To understand the syntax better, this table shows the syntax comparison between Enplated Blade and PHP:
| Enplated Blade Syntax | PHP |
|---|---|
| @include('filename') | ≈ include 'filename'; |
| {{ $variable }} | echo htmlentities($variable); |
| {{ env('value') }} | echo getAppEnvVar('value'); |
| @php | <?php |
| @endphp | ?> |
| @if (condition) | if (condition) {...} |
| @elseif (condition) | elseif (condition) {...} |
| @else | else {...} |
| @endif | (endif; exists, but not used frequently) |
| @foreach ($array as $item) | foreach ($array as $item) {...} |
| @endforeach | (endforeach; exists, but not used frequently) |
| @for (expr1; expr2; expr3) | for (expr1; expr2; expr3) {...} |
| @endfor | (endfor; exists, but not used frequently) |
We can call the View Renderer with the processTemplate('path', [params..]) function from the Controller. This function will return rendered HTML. For example, if we want to render the /views/users/details.enp file, we would write this:
checkRoute('GET', '/users/{username}/details', function() {
$username = getRequestParam('username');
$db = getDatabaseEnvConn('db');
$userId = modelCall("users", "getUserId", ["db" => getDatabaseEnvConn("db"), "username" => $username]);
// ┌ view name (located in /views/users/details.enp)
// │
$template = processTemplate("users/details", ["userId" => $userId, "pageName" => "User Details"]);
// │
// └ variable passed to the view (available for printing, for example with {{ $userId }})
});
We can return the rendered HTML to the user with the finishRender($html) function. This function will also end the script execution.
For example, if we want to render the /views/users/details.enp file, we would write this:
checkRoute('GET', '/users/{username}/details', function() {
$username = getRequestParam('username');
$db = getDatabaseEnvConn('db');
$userId = modelCall("users", "getUserId", ["db" => getDatabaseEnvConn("db"), "username" => $username]);
$template = processTemplate("users/details", ["userId" => $userId, "pageName" => "User Details"]);
// │
// └────────────┐
// ├ rendered HTML
// │
finishRender($template);
// ┌ this code will never be executed
// │
echo "Never gonna give you up\n Never gonna let you down";
});
Sometimes you do not want to return HTML to the user, but you want to return JSON / XML / CSV. Enplated MVC supports this with Resource Views. This makes it easy to create an API.
We can return data to the user with the resourceView($associative_array, "type") function. This function will return the data in the format specified in the type parameter (json, xml, csv).
For example, if we want to return the user data in JSON format, we would write this:
checkRoute('GET', '/api/users/{username}/details', function() {
$username = getRequestParam('username');
$db = getDatabaseEnvConn('db');
$userId = modelCall("users", "getUserId", ["db" => getDatabaseEnvConn("db"), "username" => $username]);
$userData = modelCall("users", "getUserDetails", ["db" => getDatabaseEnvConn("db"), "id" => $userId]);
resourceView($userData, "json");
});
Another example for XML format:
checkRoute('GET', '/api/version', function() {
resourceView([
"version" => "1.0",
"timestamp" => date("Y-m-d H:i:s")
], "xml");
});
The user can change the format of the returned data by passing the dataType POST parameter. This works only for POST requests.
This table shows which type is returned for each dataType value:
| resourceView(..., type) | dataType in POST | Returned Type |
|---|---|---|
| json | json | JSON |
| json | xml | XML |
| json | csv | CSV |
| json | incorrect value | JSON |
| xml | incorrect value | JSON |
| json | Nothing | JSON |
| xml | Nothing | XML |
function resourceView($data, $defaultType) {
//check if POST includes dataType
//$dataType = '';
//if (!isset($_POST['dataType'])) {
$dataType = $defaultType;
//} else {
// if ($_POST['dataType'] === 'json') {
// $dataType = 'json';
// } else if ($_POST['dataType'] === 'xml') {
// $dataType = 'xml';
// } else if ($_POST['dataType'] === 'csv') {
// $dataType = 'csv';
// } else {
// $dataType = 'json';
// }
//}
...
}
We can also simply use echo in the Controller. For example, if we want to create a robots.txt file, we would write this Route:
checkRoute('GET', '/robots.txt', function() {
header('Content-Type: text/plain; charset=UTF-8');
echo "User-agent: *\n";
echo "Disallow: \n";
echo "Sitemap: https://example.com/sitemap.xml";
die();
});
Enplated MVC has built-in error pages. If an error occurs, an error page is shown to the user. You can also create your own error pages.
The 403 page is not shown automatically; you have to call it in the Controller. For example, if you want to show the 403 error page when the user is not logged in, you would write this:
checkRoute('GET', '/admin', function() {
...
if (!$isLoggedIn) {
require_once "engine/errors/403.php";
die();
}
...
});
The 404 page is shown automatically when the route is not found. You can edit the /engine/errors/404.php file to customize the 404 error page.
You can also call the 404 error page in the Controller similarly to the 403 error page, but first unset the $_ENV["REQUEST"]["FOUND"] variable:
...
unset($_ENV["REQUEST"]["FOUND"]);
require_once "engine/errors/404.php";
die();
...
The 500 page is shown automatically when any error or warning occurs while the script is running. You can edit the /engine/errors/500.php file to customize the 500 error page.
The database error page is shown automatically when the database connection fails. You can edit the die_conn() function in the /engine/database/setup.php file to customize the database error page.
You can create your own error pages. Create a new file in the /engine/errors folder and call it in the Controller. Then follow the same procedure as in the 403 Error Page section.
Enplated MVC has 3 built-in debuggers. Each debugger is for a different purpose. You can enable or disable them in the .env file.
The Built-in Debugger is shown when the script is running in the development environment and an error occurs. It shows the error message, file, and line where the error occurred. It also shows all variables with their values and a code snippet around the error.
The JS Debugger logs the process of finding the correct Route. It is useful for debugging the routing system or when you rewrite some core functions.
To enable the JS Debugger, go to the .env file and change the CONSOLE_LOG variable to true.
PHP Eval Dump saves all compiled HTML and PHP code before it is processed by the PHP engine. It is useful for debugging the View files.
To enable the PHP Eval Dump, go to the .env file and change the DEBUG_BUFFER variable to true. The output files will be generated in the /debug folder.