After the successful implementation of Enplated MVC, you can start bulding your project.
MVC architecture is currently one of the most popular architectures for software development. A proper understanding of it will make it much easier for you to develop in the Enplated MVC.
MVC is an architectural pattern that separates an application into three main logical components: Model, View, and Controller. Each of these components is built to handle specific development aspects of an application. Their functionality in Enplated MVC are as follows:
A diagram is available here for illustration:
You can get a demo blog app created with Enplated MVC in the the latest release in GitHub and by downloading file demo-vX-X.zip.
.htaccess handles the redirection of all requests as follows:
index.php does the following operations:
$_ENV['REQUEST'] = [
'URI' => "...", // for example: users/veronica/details
'METHOD' => "...", // for example: GET / POST...
];
The .env is a special file located in the root of the application. It contains various global variables, database connection information and other variables for basic application settings.
The file must contain at least 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 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 PHP eval function to dump files into /debug folder. More information can be found in the PHP Eval Dumb 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 in the https://example.com URL, the BASE_URL variable should be set as BASE_URL = 'https://example.com'. If the application is running in 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 even set some later for the database connection in the Database Connection section.
Routing with 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 addresses, the other Routing File contains Routes GET /api/login, POST /api/login and GET /api/logout. Each Route has its own Controller.
RoutesCreate 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 is how we created our Routing File. Now we need to register it. Go to /routes/default.php and write this command at the beginning of the file:
registerRoutesFile(__DIR__ . '/nameOfRouteFile.php');
registerRoutesFile(__DIR__ . '/users.php');
registerRoutesFile(__DIR__ . '/management/users.php');
Go to the created Routing file from the previous section. We call a function that has 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 get the 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 function getRequestParam('name'). 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 to make some code execute every single time, you can put it in the /index.php file. For example, if you want to start a session every time the application is run, you can put the session_start() function in the beginning /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
# redirect all of the /public requests to /public folder
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^public/(.*)$ public/$1 [L]
# added line
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^cache/(.*)$ cache/$1 [L]
# otherwise, redirect to index.php
RewriteRule ^ index.php
Database connection is a very important part of any application. Enplated MVC supports connection 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 succesfull connection, you can get the connection with the getDatabaseEnvConn('db') function, more information can be found in the Get Database Connector section.
Others variables are from the .env file. The variable 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 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');
When we have MySQL / MariaDB connection, we will get the mysqli object. When we have SQLite connection, we will get the 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 to Controllers - the Model function is called by the Controller and it is up to the code in the Model Function to provide the data return. A Model could also be approximated by a class in OOP and a Model Function by 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 Routing Files). The Model is registered automatically when the Controller calls it.
Model functions are pure functions. They do not affect anything outside their function, except for changing the state of the DB, etc. The Controller will typically call one or more functions contained in the model(s), the function will then return data to the Controller.
For example, we want to create two functions for model users - one will return used id by username, other will show details for user. We would write this in the file models/users.php:
<?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 and Controller and we will call the Model functions. We can call Model function with the modelCall("model_name", "function_name", [params..]) function.
For example, if we want to create a Controller and call the user 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 the PHP code (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 will provide 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 Views (Blade) Files use a syntax similar to Laravel Blade files. The syntax 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 the .env file variable. 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 not rendered in the final HTML.
❴❴-- This code is horrible and should be rewritten, but do not show it to the user. --❵❵
@php, @endphp
Starts the 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 the conditional statement. You can write any conditional statement between the @if and @endif tags. For example, if we want to print the badge by user status, we would write this:
@if ($privilageLeve == 'admin')
<span class="badge badge-danger">Admin</span>
@elseif ($privilageLeve == 'user')
<span class="badge badge-primary">User</span>
@else
<span class="badge badge-secondary">Guest</span>
@endif
@foreach, @endforeach
Starts the loop. You can write any loop 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 the loop. You can write any loop 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 (will be available to print for exaple by ❴❴ $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 the Resource Views. This makes it easy to create an API interface.
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 variable (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");
});
User can change the type of the returned data by passing POST parameter dataType. It works only for the POST request.
This tables shows which types will be returned for which dataType value:
resourceView(..., type) | dataType in POST | Returned Type |
---|---|---|
json | json | JSON |
json | xml | JSON |
json | csv | CSV |
json | incorrect value | JSON |
xml | incorrect value | JSON |
json | Nothing | JSON |
xml | Nothing | XML |
We can also use just echos in the Controller. For example, if we want to create a robots.txt file, we would write this Route:
checkRoute('GET', '/robots.txt', function() {
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, error page will be shown to the user. You can also create your own error pages.
403 is not called automatically, but 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();
}
...
});
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 similar to the 403 error page, but unset the variable $_ENV["REQUEST"]["FOUND"] before calling the 404 error page like this:
...
unset($_ENV["REQUEST"]["FOUND"]);
require_once "engine/errors/404.php";
die();
...
500 page is shown automatically when any error or warning occurs when the script is running. You can edit the /engine/errors/500.php file to customize the 500 error page.
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 example on 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.
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 will also show all variables and their values and code snippet around the error.
JS Debugger will log process of finding 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 will show all rendered HTML and PHP code before they are processed by 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.