Reviewed-on: #44 Reviewed-by: greendavid004 <davidkatrinka1995@gmail.com>
FridgeBites Project
The FridgeBites school project is a web-based platform designed to ....
The FridgeBites was developed almost entirely from scratch using PHP, without relying on any frameworks.
The FridgeBites project was developed as part of a coursework assignment for the Subotica Tech - College of Applied Sciences program, and it is not intended for commercial use.
Libraries
The website is built entirely from scratch using pure PHP, without relying on any frameworks. This was done as an experimental project to enhance personal skills. Despite this, some libraries were used to add specific functionality:
- [PHP] PHPMailer: A library for sending emails, used for password recovery functionality.
- [PHP] FPHP: library which allows to generate PDF files with pure PHP, that is to say without using the PDFlib library.
- [JS] Swiper.js: A highly customizable library for creating sliders and carousels.
- [JS] Toastify.js: A lightweight library for creating beautiful and customizable toast notifications.
- [JS] Three.js: project is to create an easy-to-use, lightweight, cross-browser, general-purpose 3D library.
- [JS] qrcode.js: javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM. QRCode.js has no dependencies.
Installing
- Install PHP dependencies using Composer:
composer install
Configuration
The configuration template file is located in the root directory of the project as config-default.php.
You need to make a copy of this file named config.php and update it with the appropriate settings for your project.
Project Structure
The project is organized as follows:
apps/- Contains all mini-applications.{app_name}/- A single mini-application.Templates/- Page views.components/- Views for individual components.
Models/- Models for creating and managing objects.Controllers/- Controllers manage the logic and coordinate between Models/ and Templates/.components.php(optional) - Functions for handling the logic and rendering of specific components.functions.php(optional) - Utility functions specific to the application.urls.php- Defines an array of routes that map to controllers for rendering pages. These routes are later linked to the rooturls.php.
assets/- Static files such as JavaScript, CSS, and images.includes/- Core PHP scripts and libraries.media/- Uploaded files stored on the server..htaccess- Configuration file for the web server (e.g., URL rewriting).config-default.php- A template configuration file. Can be used as a base to createconfig.php.config.php- The main configuration file, containing constants and key settings.db.php- Functions for interacting with the database.functions.php- Global utility functions used across the project.index.php- The entry point for the site. Loads the router, configuration files, and more.urls.php- Combines allurls.phpfiles from mini-applications into a unified router.
MVC (Model-View-Controller)
To interact with database objects, we use classes inherited from BaseModel. For rendering pages, all logic is implemented in controllers inherited from BaseController, with a specified path to the corresponding view.
All views are connected via the router.
BaseModel
use Lycoreco\Includes\Model\BaseModel;
The BaseModel class is responsible for managing data stored and processed on the server. All other models inherit from this base class.
To create a model, the following fields need to be defined:
public $field_{column_name}: Public fields corresponding to the current table's columns, which will later be accessible.static protected $table_name: The name of the database table.static protected $table_fields: Specifies the fields and their data types (int,string,bool, orDateTime):
static protected $table_fields = [
'id' => 'int',
'user_id' => 'int',
'recovery_slug' => 'string',
'created_at' => 'DateTime',
'is_used' => 'bool'
];
static protected $additional_fields: Additional fields to be joined with the current model. The field names will later be the same as the variables. (Field names from the main table are prefixed withobj.{column_name}):
static protected $additional_fields = [
[
"field" => [
"tb2.name AS platform_title",
"tb2.icon_html AS platform_icon",
"tb2.background_color AS platform_background"
],
"join_table" => "taxonomies tb2 ON tb2.id = obj.platform_id"
]
];
static protected $search_fields: A list of fields to use for search queries. (Field names from the main table are prefixed withobj.{column_name}):
static protected $search_fields = ['obj.title'];
Methods in BaseModel:
static init_table(): Creates a new table for the model (used only insetup.php). Can usedb_query()internally.static filter(fields, sort_by, count, field_relation, offset, search, additional_fields): Retrieves an array of objects based on the specified filter.
$objects = CustomModel::filter(
[
// Field filters
[
'name' => 'obj.sales', // Field name
'type' => '>', // Comparison type (=, >, <, >=, <=, LIKE, IN). Default is "="
'value' => 20, // Value to compare
'is_having' => false // Search in WHERE or HAVING (not tied to the model)
]
],
['-obj.sales', 'obj.title'], // Sorting (prefix with "-" for descending order)
10, // Number of objects to retrieve (COUNT)
'AND', // Combine WHERE/HAVING with AND or OR
0, // Offset
'detroit', // Search value
[] // Additional fields (same as `$additional_fields`)
);
static get(fields, sort_by): Similar tofilter(), but retrieves only the first matching model. Returns false if none is found.static count(fields, search): Gets the count of objects matching the filter.is_saved(): Checks if the current object is saved in the database.delete(): Deletes the object from the database.save(): Saves the object to the database (either updates or creates a new record).
after_save(): Additional functionality to execute after saving (optional).valid(): Validates the data. If there are issues, returns an array of string messages.
BaseController and Page Rendering
use Lycoreco\Include\BaseController;
For managing a specific page, we use a class that extends the BaseController, located at /includes/BaseController.php.
Within this class, we can implement and modify the following methods and fields:
$template_name- The path to the view. Typically located in theTemplates/folder of the current application.$allow_role- Specifies which user role is permitted to access the view (null,"user", or"admin").get_model()- Retrieves a specific model associated with the page (e.g.,SinglePostController).get_context_data()- Passes an array of variables in$contextto the view.distinct()- Provides additional functionality before rendering the view.post()- Executes only if the page is accessed via the POST method.
Router and URL Handling
The main Router class is located at includes/Routing/Router.php. This class serves as the core for routing and displaying the appropriate controller based on the path.
Classes Overview
namespace Lycoreco\Includes\Routing;
Router: The primary class used for determining which controller to render based on the requested path.Path: A helper class where the path to a controller and the link name are specified.- Exception Classes: Used for handling errors, including:
PageError(general, 500)PermissionDenied403Unauthorized401BadRequest400NotFound404
Defining Routes
In mini-applications, routes are defined in a urls.php file, where paths and controllers are specified. Variables like slug or id can be passed using placeholders such as [:string] or [:int].
use Lycoreco\Apps\MyApp\Controllers;
$my_app_urls = [
// path, controller, name
new Path('/item/[:string]', new Controllers\SingleController(), 'single'),
new Path('/home', new Controllers\HomeController(), 'home'),
];
Integrating Routes into the Main Application
The array of mini-application routes is then added to the root directory's urls.php file, specifying the group name. Additionally, error controllers (e.g., "default", 404, 500) can also be defined:
use Lycoreco\Includes\Routing\Router;
require APPS_PATH . '/my_app/urls.php';
Router::includes($my_app_urls, "myapp");
Router::set_error_controller('default', new ErrorController());
Ajax
The website supports AJAX functionality through a dedicated mini-application called ajax and an AjaxController (accessible via /ajax). This setup enables features such as:
- Adding items to the wishlist
- Real-time search
- Adding items to the cart
- Viewing the cart
How It Works
-
Data Processing: In the
AjaxController, data is sent via the request body in JSON format. The data is then processed by a function specified in the action attribute of the JSON object. -
Creating a Custom Action: To create a custom action, you need to add a new function in
apps/Ajax/ajax-actions.php.- The function must be named
ajax_{your_action_name}. - It takes an associative array from the args attribute of the JSON object as its argument.
- The function should return an array (either associative or indexed), which will be encoded into JSON for the client.
- Error Handling: To trigger an error, return
get_ajax_error($message, $error_code)
- The function must be named
-
Frontend Interaction: On the frontend, you can use the
fetch()API or an existingsendAjaxfunction. The sendAjax function accepts the following parameters:action— The name of the action to be executed.args— An object containing arguments to be passed to the action.onLoad— A callback function executed when the request starts.onSuccess— A callback function executed upon a successful response.onError— A callback function executed if an error occurs.
Example: Custom Action in PHP (backend) and Javascript (frontend)
PHP (backend)
// Example action: Return a number with a message
function ajax_my_number($args) {
$result = array();
// Validate the input
$num = $args['number'];
if (!isset($num) || !is_numeric($num)) {
return get_ajax_error("Invalid number", 400); // Return error for invalid input
}
// Prepare the response
$result['message'] = "My number is " . $num;
return json_encode($result, JSON_PRETTY_PRINT); // Return JSON-encoded result
}
Javascript (frontend)
sendAjax(
// action
"my_number",
// args
{
number: 5
},
// onLoad
() => {
console.log('loading...');
},
// onSuccess
(result) => {
console.log(result.message);
},
// onError
(error) => {
console.log(error.message);
});