This is a framework created for my personal use. Nothing too fancy, it's simply a result of me dabbling with PHP in my spare time. The syntax was inspired by Laravel.
Features:
- a router for dynamic and static routes
- a MySQL query builder (supports only SELECT statement and CTE for now)
- utilizes the MVC design pattern with middlewares
- head element is accessible and modifiable in every component of the page
- environment variables support
-
Core- the backbone of the framework -
Helpers- contains helper functions automatically loaded at the beginning ofindex.php.└ debuggers.php- debugging functions└ misc.php- miscellaneous helper functions -
Controllers- contains controllers -
Models- contains models -
Views- contains views└ Partials- contains partial views -
Middlewares- contains middlewares -
public└ css- contains css files└ js- contains js files└ static- contains static files -
env.php- a configuration file for environmental variables -
middlewares.php- a file for registering middlewares -
routes.php- a file for registering routes -
index.php- the initializing file -
.htaccess- redirects all requests toindex.php.
The default file for environment variables is .env. You can change the configuration in env.php.
Required env variables:
DB_HOSTDB_USERDB_PASSWORDDB_NAME
Additional env variables:
BASE_DIR- the directory in which the app lives on the server (default:/)CSS_PATH- the path to css files (default:public/css)JS_PATH- the path to js files (default:public/js)STATIC_PATH- the path to static files (default:public/static)
Inside of the routes.php file you can define routes, just like the name suggests. Here is an example:
use Core\Route;
use Controllers\Catalog;
Route::get(
'/',
function () {
echo 'Hello World!';
}
);
// would match: category/123/13
// would NOT match: category/asd24
Route::get(
'/posts/{id[]}',
[Catalog::class, 'index'],
['id' => '[0-9]+']
);
The Route class has corresponding CRUD methods: get, post, delete, put (and in addition patch). The parameters of these methods are as follows:
path- as the name impliescallback- a function or an array with a class and method's nameparameter_constraints- an associative array describing the regex pattern of parameters that has to be met
Available parameter types:
- one obligatory parameter
{param} - one optional parameter `{param?}
- multiple obligatory (at least one) parameters
{param[]} - multiple optional parameters
{param[]?}
To add an error route use the ErrorRoute class like so:
ErrorRoute::add(
ErrorRoute::CODE_MAP['NOT_FOUND'],
[View::open('404.php'), 'load']
);
ErrorRoute::add parameters:
code- code of the error, you can use the additional CODE_MAP enum to improve code readabilitycallback- a function or an array with a class and method's name
Are stored in the Middlewares directory. Example file:
namespace Middlewares;
class showRequest {
function handle($request){
dwd('Here is the request: ', $request);
}
}
Now, you have to register the middleware in middlewares.php:
use Core\Middleware;
use Middlewares\showRequest;
// the first argument is middleware's alias, the second one is a reference to the method
Middleware::add(
'showRequest',
[showRequest::class, 'handle']
);
And you can finally add the middleware to a route like so:
Route::get(
'/',
[Home::class, 'index'],
)->middleware('showRequest');
Controllers should be put inside of the Controllers directory. Here is an example of a controller:
namespace Controllers;
use Core\{View, Request};
class Home {
public function index(Request $request){
$title = 'Hello World!'
View::open('index.php')->load(['title' => $title]);
}
}
the $request argument is a class, it contains:
- parameters - extracted dynamic parameters
- path - the path without the base directory (if one was defined)
- method - the HTTP method
- data - query variables
- misc - initially
NULL, it can contain additional information that you want to pass further on. It is more useful in middlewares rather than in controllers. set_path(string $value)- changes the path parameter, returns a URL with the new path.update_data(string $key, string $value)- changes value of a query string variable, returns the current URL with a new query string attached.
If you want to pass variables to a view. You can do this by passing an associative array - ['variable_name' => $value] - to the load method as shown in the code snippet above.
Views should be put inside of the Views directory. To initialize the file you have to call View::open('filename'), the method returns a class with the following methods:
load(array $variables)- renders the filerenderString(array $variables)- returns the rendered HTML as a string
An example file could look like this:
<?php
use Core\{Partial, View};
$head->css('home.css', true)->title($title);
?>
<?php Partial::open('header.php')->load(); ?>
<main>
<?php
View::open('home/gallery.php')->load();
View::open('home/featured.php')->load();
?>
</main>
<?php
View::open('home/aboutus.php')->load();
Partial::open('footer.php')->load();
?>
As you can see we can add properties to the head using chainable methods. The $head variable is accessible in every subview/partial view and its descendants.
Methods of $head:
title(string $title)- set up the title tagdescription(string $description)- for description meta tagmeta($content)- for other meta tagscss(string $src, bool $local = false)- if you passtrueas the second parameter the path will be relativized.script(string $src, bool $local = false)- for js scripts, loads them withdeferattribute to ensure that they work after the page load. Same case as with thecssmethodjs(...$args)- alias forscript
Partials work exactly the same way as views do, the only difference is that they should be stored inside of the Views/Partials directory and should be created by the Partial class as shown in the above code snippet.
Are stored in the Models directory. They are simply classes belonging to the Models namespace. They can be imported to the controllers like so use Models\Test as TestModel.
Example usage:
use Core\QueryBuilder;
function getProductImage($id){
$result = QueryBuilder::select(['image_name'])
->from('product_image')
->where('product_ID=?', [$id])
->orderBy('main', 'desc')
->execute();
return $result;
}
QueryBuilder::select(columns, variables*) - if you want to use aliases for columns, pass an associative array as the columns parameter like so: ['alias' => 'column'].
where(condition, variables*)- only one per query (otherwise the query will get jumbled), if you want add other conditions use the following methods:orWhere(condition, variables*)andWhere(condition, variables*)
optionalWhere(condition, variables*)- it only applies the clause if the amount of variables given corresponds to the amount of parameters (?) in the "condition". Same case as with thewheremethod. If you have already usedwhere, don't call this method. Otherwise you will get a faulty query as a result. You should use the following methods instead:orOptionalWhere(condition, variables*)andOptionalWhere(condition, variables*)
join(table, alias, condition)- you can not only join a table but also a subqueryleftJoin(table, alias, condition)rightJoin(table, alias, condition)
groupBy(...columns)- you can group by as many columns as you want, simply pass them as arguments one after another. The order of grouping will be the same as the order of passed arguments.orderBy(column, order*)- sorts records in ascending order by default, if you want to sort in reverse, pass 'DESC' as the second argument.limit(from, to*)- limit from, to.tois optional.having(condition, variables*)- works exactly like thewheremethod but forHAVINGclauseorHaving(condition, variables*)andHaving(condition, variables*)
optionalHaving(condition, variables*)orOptionalHaving(condition, variables*)andOptionalHaving(condition, variables*)
If you want to execute the query right away, invoke the execute method at the end. If you want to get the query as a string, use getQuery.
variables are values bound to parameters from query or parts of query - ?. They are optional, hence the asterisk.
You can pass a subquery to QueryBuilder::select, SelectBuilder->from, or any join method. Here is an example with QueryBuilder::select:
QueryBuilder::select([
p.ID,
'image_name' => QueryBuilder::select(['p_i.image_name'])
->from('product_image', 'p_i')
->where('p_i.product_ID = p.ID')
->orderBy('p_i.main', 'DESC')
->limit(1)
])->from('product', 'p')->execute();
Notice that there is no execute or getQuery at the end of a sub-query.
You can call the QueryBuilder::exists(table, alias) method to return a SELECT statement with an EXISTS operator. The table parameter can be either a name of a table or a subquery. The method is build upon QueryBuilder::select, so it has access to all of its methods.
You can call the QueryBuilder::count(table) method to return a SELECT statement with a COUNT function. The table parameter can be either a name of a table or a subquery. The method is build upon QueryBuilder::select, so it has access to all of its methods.
$category_id = 7;
QueryBuilder::with('cte')
->following(
QueryBuilder::select(['id', 'name', 'parent'])->from('cte')
)
->recursive(
QueryBuilder::select(['id', 'name', 'parent'])->from('category')->where('id=?', [$category_id]),
QueryBuilder::select(['c.id', 'c.name', 'c.parent'])->from('category', 'c')->join('cte', '', 'c.id = cte.parent')
)
->getQuery();
# Would return:
# WITH recursive cte AS
# (
# SELECT id,name,parent FROM category WHERE id=7
# UNION ALL SELECT c.id,c.name,c.parent FROM category as c
# INNER JOIN cte on c.parent = cte.id
# )
# SELECT GROUP_CONCAT(id) as array FROM cte
Just like with SELECT operation you can either execute the query immediately with execute or use getQuery.
To create a nonrecursive WITH clause, you can call nonrecursive(...$anchors).
These functions are available everywhere. You can extend helper files and add your own functions.
get_static($path)- returns a path to a file from the static folderlocal_href($url)- addsBASE_DIRin front of the relative URL.local_href_with_query_string($url)- as the name implies.redirect($url, $statusCode = 303)- as the name implies.
You can use dwd(mixed ...$data) or dd(mixed ...$data) functions to dump info for debugging purposes. In the case of dwd it will be dumped at the end of the page.