I would like to ask for some tips, on how solve this problem. I\'m trying to build my own MVC website. I learned the basics of the URL.
http://example.com/bl
I think you are over thinking this one....
So the controller/resource is a blog....the method that all of them should run from is "read" (using crud...I usually call it data but basically its your select). Now just have your method accept a category value that automatically gets mapped based on the url....
here is a sample...completely untested but just to show you the idea (using pdo)
public function data($id = 0, $category = 0){
if (isset($id) AND $id != 0){
$bind = array(":id", $id);
$results = $db->query("SELECT * FROM blog WHERE blog_id = :id", $bind);
return $results[0];
} else if (isset($category) AND $id != 0){
$approved_categories = array("cosplay","game","movie","series");
if (in_array($category, $approved_categories)){
$bind = array(":cat", $category);
$results = $db->query("SELECT * FROM blog WHERE blog_cat = :cat", $bind);
}
return $results;
}
}
FYI: There are several things that you are doing wrong. I will try to go through each of them and explain the problems, the misconceptions and the possible solution(s).
From the look of you posted code, it's obvious, that you have a single class, which is responsible the following tasks:
In OOP there is this thing, called: Single Responsibility Principle [short version]. Basically it means that a class should be handling one specific are thing. The list above constitutes at least 4 different responsibilities for your Autoload
class.
Instead of what you have now, each of these general tasks should be handled by a separate class. And in case of autoloader, you could get away with a single function.
Part of the problem that I see is the confusion about how autoload actually works in PHP. The call of include
or require
doesn't need to be done where the instance will be created. Instead you register a handler (using spl_autoload_register() function), which then is **automatically* called, when you try to use previously-undefined class.
The simplest example for it is:
spl_autoload_register( function( $name ) use ( $path ) {
$filename = $path . '/' . $name . '.php';
if ( file_exists( $filename ) === true ) {
require $filename;
return true;
}
return false;
});
This particular example uses anonymous function, which is one of features that was introduced in PHP 5.3, but the manual page for the spl_autoload_register()
will also show you examples how to achieve the same with objects or ordinary functions.
Another new feature that is closely related to autoloading is namespaces. In this context the namespaces would give you two immediate benefits: ability to have multiple classes with same name and options to load class file from multiple directories.
For example, you can have code like this:
$controller = new \Controllers\Overview;
$view = new \Views\Overview;
$controller->doSomething( $request );
.. in this case you can have autoloader fetching classes from /project/controllers/overview.php
and /project/views/overview.php
files respectively. Because the spl_autoload_register()
will pass "\Controllers\Overview"
and "\Views\Overview"
to the handler function.
There is also a FIG recommendation for how to implement autoloaders. You can find it here. While it has some significant problems, it should provide you with good base on which to build upon.
It is no secret, that Apache's mod_rewrite is quite limited in what it can do with pretty URLs. And, while it's a widespread server, it is not the only option for webservers. This is why for maximum flexibility PHP developers opt to handle URLs on the PHP end.
And the first thing any newbie will do is explode('/', ... )
. It is a natural choice, but you will soon notice that it is also extremely limited in what it can really do. The routing mechanism will start to grow. At first based on count of segments, later - adding different conditional values in segments, that require different behavior.
Essentially, this will turn in huge, fragile and uncontrollable mess. Bad idea.
Instead what you should do is have a list of regular expressions, that you match against given pretty URL. For example:
'#/(?P<resource>[^/\\\\.,;?\n]+)/foobar#'
The above defined pattern would match all the URL that have two segments, with some text in first segment and "foobar"
in the second ... like "/testme/foobar"
.
Additionally you can link each pattern with corresponding default values for each match. When you put this all together, you might end up with configuration like this (uses 5.4+ array syntax, because that's how I like to write .. deal with it):
$routes = [
'primary' => [
'pattern' => '#/(?P<resource>[^/\\\\.,;?\n]+)/foobar#',
'default' => [
'action' => 'standard',
],
],
'secundary' => [
'pattern' => '#^/(?P<id>[0-9]+)(?:/(?P<resource>[^/\\\\.,;?\n]+)(?:/(?P<action>[^/\\\\.,;?\n]+))?)?$#',
'default' => [
'resource' => 'catalog',
'action' => 'view',
]
],
'fallback' => [
'pattern' => '#^.*$#',
'default' => [
'resource' => 'main',
'action' => 'landing',
],
],
];
Which you could handle using following code:
// CHANGE THIS
$url = '/12345/product';
$current = null;
// matching the route
foreach ($routes as $name => $route) {
$matches = [];
if ( preg_match( $route['pattern'], $url, $matches ) ) {
$current = $name;
$matches = $matches + $route['default'];
break;
}
}
// cleaning up results
foreach ( array_keys($matches) as $key ) {
if ( is_numeric($key) ) {
unset( $matches[$key] );
}
}
// view results
var_dump( $current, $matches );
Live code: here or here
Note:
If you use'(?P<name> .... )'
notation, the matches will return array with'name'
as a key. Useful trick for more then routing.
You probably will want to generate the regular expressions for the matching from some more-readable notations. For example, in configuration file, this expression:
'#^/(?P<id>[0-9]+)(?:/(?P<resource>[^/\\\\.,;?\n]+)(?:/(?P<action>[^/\\\\.,;?\n]+))?)?$#'
.. should probably look something like
'/:id[[/:resource]/:action]'
Where the :param
would indication an URL segment and [...]
would signify an optional part of URL.
Based on this you should be able to flesh out your own routing system. The code fragments above is just example of simplified core functionality. To get some perspective on how it might look when fully implemented, you could look at code in this answer. It should give you some ideas for your own API version.
It is quite common mistake to bury the execution of controllers somewhere deep in the routing class (or classes).This causes two problems:
Routing is a task which even in custom-written application will naturally gravitate toward the "framework-ish" part of codebase.
The (really) simplified versions would look like:
$matches = $router->parse( $url );
$controller = new {'\\Controller\\'.$matches['controller']};
$controller->{$matches['action']( $matches );
This way there is nothing that requires your routing results to be used in some MVC-like architecture. Maybe you just need a glorified fetching mechanism for serving static HTML files.
You are looking at it the wrong way. There is no need for dynamically adding methods to controller. In your example there is actually one controller method ... something along the lines of:
public function getCategory( $request ) {
$category = $request->getParameter('category');
// ... rest of your controller method's code
}
Where $category
would end up containing "cosplay"
, "game"
, "movie"
, "series"
or any other category that you have added. It is something that your controller would pass to the model layer, to filter out articles.
These days, since everyone (well .. everyone with some clue) uses composer, for autoloading the best option is to use the loader that is comes bundled with composer.
You simply add require __DIR__ . '/vendor/autoload.php'
and with some configuration it will just work.
As for routing, there are two major "standalone" solutions: FastRoute or Symfony's Routing Component. These ones can be included in you project without additional headaches.
But since some of people will be using frameworks, each of those will also contain capability of routing the requests.
If you want to learn more about MVC architectural pattern, I would strongly recommend for you to go though all the materials listed in this post. Think of it as mandatory reading/watching list. You also might find somewhat beneficial these old posts of mine on the MVC related subjects: here, here and here
P.S.: since PHP 5.0 was released (some time in 2004th), class's variables should be defined using
public
,private
orprotected
instead ofvar
.