? Overview
This project module encompasses the core logic for managing and displaying collectible items, separating the duties of data interaction and front-end presentation into two specialized classes: CollectibleRepo and CollectibleRender. The goal is to provide fast, filtered access to collectible data and generate dynamic HTML output for both lists and detailed views.
Collectibles
One of my hobbies is collecting model cars and Funkos. I've been into model cars since 2013, with Funkos coming some 6 years later. But keeping track of them all is chaotic, and being able to virtually share them with friends and family was something I wanted to do for a while. That's how this project started to exist.
A comprehensive database in MySQL was setup to accommodate all the important fields for model cars, particularly those of racing. Each entry can be filtered by their scale, team, driver, category, or maker. The backend is powered by PHP, with a simple UI written in HTML and CSS focused on delivering the content as fast as possible.
A secondary version of the project, Collectibles Showcase, was written for Web Development course during my time at SAIT. It uses React, Next.JS, Tailwind CSS, deployed to Vercel, while sharing the same database and assets of the main website via REST API.
⚙️ Technical Stack
| Area | Technologies |
|---|---|
| Backend | PHP, PDO, MySQL |
| Database Interaction | Secure PDO prepared statements and exception handling |
| Frontend Output | HTML generation using output buffering (ob_start()) |
The CollectibleRepo class requires the Database.php file. It initializes its connection using PDO and ensures that PDO is configured to throw exceptions upon errors by setting PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION.
? Architecture & Design
The architecture follows a clear separation of concerns using two main classes in an Object-Oriented Programming (OOP) design:
CollectibleRepo: Responsible exclusively for querying the MySQL database. It provides methods for retrieving various filtering dimensions and fetching collectible records. Data is consistently returned as associative arrays.CollectibleRender: Instantiated with aCollectibleRepoobject. This class focuses on taking data retrieved by the repository, sanitizing it, and generating the necessary HTML output for filters, lists, and detail pages.
Data retrieval within the CollectibleRepo involves complex joins across multiple miniature-related tables (mini_miniatura, mini_piloto, mini_equipe, mini_escala, etc.) to gather comprehensive details.
?️ Features
-
Dynamic Filter Generation: The
CollectibleRender::buildFiltersmethod constructs the HTML form for filtering items, drawing available options directly from the repository. Filter categories include:- Drivers (
piloto) - Makers (
fabricante) - Teams (
equipe) - Years (
ano) - Scales (
escala)
- Drivers (
-
Data Retrieval Methods: The
CollectibleRepoprovides functions to get lists of items (getCollectibles), total item counts (getCollectiblesCount), and detailed information for a single item (getCollectible). Detailed data includes fields likeyear,number,maker,category,scale,additional_info(obs),SKU(id2),championstatus,limited_editionstatus,driver,characteristics,contents,model,team,figure_name, andfigure_series. -
List View Rendering: The
displayCollectiblesListfunction renders a grid. If a collectible is identified byfigure_name, the title combines the name andfigure_series; otherwise, the title includes thedriver,year,team, andmodel. -
Detail View Rendering: The
displayModelfunction renders an individual collectible's information. It displays the primary image and iterates through all available fields (exceptimageandid), formatting them for display (e.g., "DRIVER: NAME").
? Challenges & Solutions
- Challenge: Ensuring consistency in the structure of data returned for filter lists, even if the underlying database columns differ.
- Solution: All filter retrieval methods (
getDrivers,getMakers, etc.) usearray_mapto explicitly return only the requiredvalueandkeyelements, dropping extraneous columns likereleased_at.
- Challenge: Implementing filtering logic that supports dynamic user input while integrating with existing repository functions.
- Solution: The
getFiltersFromPostfunction processes POST and GET parameters (likefilter-driver,filter-maker,model), sanitizes them, and builds an SQL fragment string (e.g.," AND ... ") which is then passed directly to the repository functions.
? Security & Best Practices
Sanitization: The CollectibleRender class includes a private sanitize method using htmlspecialchars with ENT_QUOTES and UTF-8 encoding to prevent Cross-Site Scripting (XSS) attacks on rendered output. This sanitization is applied to filter inputs before they are used to build SQL fragments and applied to labels/values rendered in HTML options. Error Handling: All database interaction methods within CollectibleRepo are wrapped in try-catch blocks to gracefully handle PDOException errors, returning empty arrays or 0 counts upon failure. Error conditions for fetching a single collectible that results in a 404 response also lead to termination (die()).
?️ Collectibles Interface Preview
The following structure shows how items are displayed in the list and detail views, incorporating image placeholders:
Collectibles List View
This view utilizes the displayCollectiblesList function.
<section class="models-grid">
Models List
<article>
<img src="/images/collectibles/[Placeholder_Image_File_1]" alt="[Placeholder Title 1]" title="[Placeholder Title 1]" />
<a href="page/collectibles-details?model=[Placeholder_ID-Slug_1]">
<p>[Placeholder Driver Year Team Model or Figure Name (Series)]</p>
</a>
</article>
<article>
<img src="/images/collectibles/[Placeholder_Image_File_2]" alt="[Placeholder Title 2]" title="[Placeholder Title 2]" />
<a href="page/collectibles-details?model=[Placeholder_ID-Slug_2]">
<p>[Placeholder Driver Year Team Model or Figure Name (Series)]</p>
</a>
</article>
<!-- ... more collectibles ... -->
</section>
Collectible Detail View
This view utilizes the displayModel function. The title will be driver year team model scale or figure_name (figure_series).
<section class="collectible-detail">
<img src="/images/collectibles/[Placeholder_Main_Image_File]"
alt="[Placeholder Detail Title]"
title="[Placeholder Detail Title]"
onclick='openLightbox("[Placeholder_Main_Image_File_Lightbox_Path]")' />
<ul>
<li>*YEAR*: [Placeholder Year]</li>
<li>*MAKER*: [Placeholder Maker]</li>
<li>*CATEGORY*: [Placeholder Category]</li>
<li>*SCALE*: [Placeholder Scale]</li>
<!-- Other available fields are listed dynamically -->
<li>*DRIVER*: [Placeholder Driver]</li>
<li>*LIMITED EDITION*: YES</li>
<li>*SKU*: [Placeholder SKU]</li>
</ul>
</section>
<section class="gallery">
<!-- Additional images rendered by displayImages -->
<img src="/images/collectibles/[Placeholder_Additional_Image_1]"
alt=""
title=""
onclick='openLightbox("[Placeholder_Additional_Image_1_Lightbox_Path]")' />
<img src="/images/collectibles/[Placeholder_Additional_Image_2]"
alt=""
title=""
onclick='openLightbox("[Placeholder_Additional_Image_2_Lightbox_Path]")' />
</section>
? Results & Learnings
The creation of the CollectibleRepo and CollectibleRender demonstrates effective use of PHP's OOP principles to create a modular, maintainable system for dynamic content serving. By clearly separating the database abstraction layer from the presentation layer, the system maintains flexibility for future updates to either the data schema or the front-end design. This project served as a practical exercise in implementing robust database retrieval logic and applying necessary security measures through input sanitization.
Future roadmap
- ✅Pagination for unfiltered results





