I am not quite sure if this is done by rewriting the URL, but I did not know how to title it accordingly.
Here is my problem. I am currently working on a website project where I am using a abstraction of the MVC Framework (it is mainly for learning the Framework).
This is how my folder-structure looks like:
/controller/
|--indexcontroller.php
/myaccount/
|--/controller/
|--indexcontroller.php
|--index.php
/globals/
|--framework.php
/templates/
/options/
|--settings.php
|--config-www.php.inc
So currently I am using autoloader to load the classes which are needed. The index.php in the myaccount folder inherits the Framework class which should handle the class loading:
$urlparts = explode("/", $_SERVER['REQUEST_URI']);
$urlparts2 = explode("?", $urlparts[2]);
$class = str_replace(".php", "", $urlparts2[0]);
if ($class == "") {
$class = "index";
}
$letters_first = substr($class, 0, 1);
$letters_last = substr($class, 1, strlen($class));
$class = strtoupper($letters_first).$letters_last."Controller";
if (class_exists($class)) {
$object = new $class();
} else {
echo "Problem: class $class does not exist.";
}
The problem i have at the moment is, that I can only use "http://www.url.com/myaccount/" which is loading the indexcontroller.php in the controller folder from myaccount (which is fine). But I want also be able to use "http://www.url.com/myaccount/profile", where then instead "profilecontroller.php" in the controller folder from myaccount should be called.
How can I do this? URL rewriting? Or am I doing it totally wrong?
This might be a bit overwhelming at first but if you get it, it will make you life a lot easier.
1 ) Take a look at an existing framework like symfony, codeigniter, etc to see how they get the routing done.
2 ) Try to reuse these "components" (like routing form symfony) through composer so you won't have to do it yourself.
This way:
You won't have to write it all yourself.
You are using the hot stuff, like composer :)
You still learn by looking at the implementation of other frameworks
I hope this helps a bit :).
Related
I am writing my own php mvc framework (just for training). The question is how to handle exception when the requested controller doesn't exist? Should I call 404 class or create and show new View from Router? I'll be glad if you have any advices for me!
Here are my autoload.php:
function __autoload($class)
{
$filename = __DIR__ . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if (file_exists($filename))
{
require $filename;
}
else
{
throw new \Exception('The file doesn\'t exists!');
}
}
and Route.php:
namespace App;
class Route
{
public static function start ()
{
$controller_name = 'News';
$controller_action = 'Index';
if (isset($_GET['furl']))
{
// Getting rid of spaces
$url = str_replace(' ', '', $_GET['furl']);
if (substr($url, -1) == '/')
{
$url = substr($url, 0, count($url) - 2);
}
$arr = explode('/', $url);
foreach($arr as &$value)
{
$value = strtolower($value);
$value = ucfirst($value);
}
$controller_action = $arr[count($arr) - 1];
unset($arr[count($arr) - 1]);
$controller_name = implode('\\', $arr);
}
$controller_name = '\App\Controllers\\' . $controller_name;
try
{
$controller = new $controller_name();
}
catch (\Exception $e)
{
//HELP ME PLS!
}
$controller->action($controller_action);
}
}
No matter how many web frameworks, routers, autoloaders, etc are there already: keep doing what you think it's right for you and suitable to your momentarily understanding level, in order to LEARN. Actually, by confronting yourself with problems arised along the process of implementing yourself different parts of your application, you will not only gain the opportunity to learn and discover new things, but also to learn how and what to study in the already existing frameworks' design.
Study the PHP Standard Recommendations (the ones marked as "accepted"). Especially PSR-1,2,4,7. They are used by many frameworks and PHP projects. Read FAQs to find out more about the project itself.
Autoloader:
The PSR-4 provides a link with examples at the document end.
#mike suggested, that you should use the Composer autoloader. I agree with him and I strongly recommend it to you too. BUT I suggest you to do this only after you correctly implement and make use of your own autoloader (PSR-4 conform). Why? You definitely need to learn how the autoloading process works. And in some future situations you will still need your own autoloader implementation, even after Composer is installed and running.
Also be aware that you must not raise any exceptions from autoloader itself!
Router:
Btw, your class should be called "Router".
The router should not be responsible for validating the controller class/file and the action, nor for calling the action. These tasks are part of the "front controller" responsibilities. Your router should just return the components resulted after parsing, e.g. "exploding" the request URI ($_GET['furl']), in some form (as a Route object (with them as properties), as array, etc). These components are the controller name, the action name, the action parameters list (NB: the action parameters are not the query string parameters). The front controller uses them further to validate/access the controller class/file and its action and to call the action.
But please note that a router works actually in other way. In short: it matches (e.g. compares) the request method (GET, POST, etc) and the request URI against an existing (e.g. predefined by you) list of route definitions. A route definition contains the infos related to a specific controller, action, etc. If the HTTP method and the request URI "correspond" to one of the route definitions, then the router returns the matched definition's components to the front controller (in some form: as object, as array, etc).
For more details describing this principle see:
How to load classes based on pretty URLs in MVC-like page?
FastRoute
Aura.Router
Front controller:
It can be a class, but it can also be just vanilla code in the entry point of your app (index.php, bootstrap.php, etc). In the latter case, the front controller code should reside in a file outside of the document root of the app. For example in a bootstrap.php file, which is to be just included in index.php - whereas index.php resides inside the document root.
"controller/action not found" specific handling:
If a controller, or an action is not found/valid, then call a predefined action (for example displayError) of a predefined Error controller, which informs the user that, for a specific part of his request (actually of his provided request URI), no resource was found.
For example, consider that the user provided the request URI www.example.com/books/show/12. Conform to your app workflow the controller is Book, the action (e.g. the controller's method) is show and the action parameter is 12 (the value is passed as argument to the show method and defined as $bookId parameter in it). But, if the controller class is not defined, or no controller file exists, then the front controller should call the action displayError of Error controller, which should display a message like No resource found for your 'book' request. A similar info should be displayed when the show method is not yet defined in the Book controller.
Note that, if the Error controller or its action is not found/valid, then the PHP engine raises a corresponding error/exception. If you follow the next links I provided, you'll end up implementing three custom error/exception handling functions (referenced by set_error_handler, set_exception_handler and register_shutdown_function, respectively). They will catch and handle the described situation properly.
To read: Manage the errors of a framework
General error/exception handling in MVC:
Here are some good resources:
Again: Manage the errors of a framework
Error logging, in a smooth way
Error reporting basics
The (im)proper use of try..catch
Other MVC related resources:
Build a PHP MVC Application (Just for the start...)
Dependency Injection and Dependency Inversion in PHP
MVC for advanced PHP developers (A further list of resources)
Tom Butler's Programming Blog. MVC, PHP, Best practices
Clean, high quality code
P.S: Avoid the use of statics, globals, singletons. Why? Read here and here, for example.
Good luck.
I have been experimenting with Slim for a few days now and there is a lot that I like. However, there is one thing that nags me - when Slim is used to build a REST API it insists on putting everything into one single .php file - or even worse, a load of anonymous functions (one for each exposed REST method).
This works, that is not the issue. However, does it not mean that when used for building any but the most trivial of APIs you are imposing an unnecessary burden on the server by getting it to load and parse a potentially really big PHP file when only a tiny percentage of its code is relevant?
If yes, then it begs the next question - I am a newbie to micro frameworks - is there a micro framework that does things in a way that avoids this issue?
I'm unsure whether you are looking for a framework for a complete website or for an api alone. If latter I can recommend restler, which is a very simple, OO and stable package for developing API's.
On the other hand Laravel has built in resource controllers to ease the api development, it also has a very fast growing community.
If you're looking for both a website framework ánd an api framework in one, my guess is it will be very difficult combining that into one micro framework. Laravel/Symfony and other frameworks will eventually be needed especially if you expect growth in the project.
I made a REST API using AltoRouter:
http://altorouter.com/
I like the fact that after one route matches I can choose how to make the call, so I can point to the folder where I have all the REST controllers divided in several well organized files.
http://altorouter.com/usage/processing-requests.html
An example:
function rest_data($data, $format)
{
header("Cache-Control: no-cache, must-revalidate");
header("Expires: 0");
header('Content-Type: ' . $format);
if (is_object($data) && method_exists($data, '__keepOut'))
{
$data = clone $data;
foreach ($data->__keepOut() as $prop)
{
unset($data->$prop);
}
}
$options = 0;
$json=json_encode($data, $options);
echo $json;
}
$router = new AltoRouter();
$router->setBasePath('/rest');
$router->map('GET|POST','/', 'home#index', 'home');
$router->map('GET','/sample_users/all', 'sample_user#all', 'sample_users_get');
// match current request
$match = $router->match();
if($match && $match['name']=='home')
{
// write some intro
exit();
}
if($match)
{
$target=$match['target'];
list($class_name,$function_name) = explode("#",$target);
require($conf["src.path"].'/rest/'.$class_name.'.php');
foreach($_GET as $k=>$v) $match['params'][$k] = $v;
$controller = new $class_name;
$res = $controller->$function_name($match['params']);
rest_data($res,RestFormat::JSON);
}
If you are not so keen on the Slim PHP framework might I suggest something like Symfony ? Personally I like the way Slim works, and suits what I need to build right down to a T - however I can see your frustration with creating a RESTful solution. Symfony on the other hand breaks things up a little more so you have less fat controllers.
If Symfony isn't right for you I guess you could pull out a heavyweight ike Laravel?
I wrote an Auth plugin to check if the user is logged in. No unlogged user should be able to visit anything in the app except the login page.
So I have this in the file application/modules/user/plugins/Auth.php:
class User_Plugin_Auth extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
if (Zend_Auth::getInstance()->hasIdentity()
|| $this->getRequest()->getActionName() == 'login') return;
$request->setModuleName('user');
$request->setControllerName('auth');
$request->setActionName('login');
}
}
Then I made this in the application.ini:
pluginPaths.User_Plugin = APPLICATION_PATH "/modules/user/plugins/"
resources.frontController.plugins[] = "User_Plugin_Auth"
However, no matter how I move the Auth.php file and no matter the name, I always get Fatal error: Class 'User_Plugin_Auth' not found. Please help me, I have wasted more than one hour on this and it's frustrating.
I think the problem is to do with the filename. I would try creating copies of the files in these locations
application/modules/user/plugins/User_Plugin_Auth.php
application/modules/user/plugins/User/Plugin/Auth.php
naturally you only need one of them, so delete the others once you have found the one that works.
If that doesn't help that the syntax I have in my config (Fam is just the codename of the project)
resources.frontController.plugins.layout = "Fam\Controller\Plugin\Layout"
resources.frontController.plugins.route = "Fam\Controller\Plugin\Route"
as pointed out in comments that is php 5.3 - this should work in older versions assuming include paths are configured
resources.frontController.plugins.layout = "Fam_Controller_Plugin_Layout"
resources.frontController.plugins.route = "Fam_Controller_Plugin_Route"
which maps to the files in my library, eg
APPLICATION_PATH "/../library/Fam/Controller/Plugin/Layout.php"
For reference in this project my Zend files are in
APPLICATION_PATH "/../library/Zend"
so adjusting the files to be in the relative place should do the trick
is your autoloader configured? I have
autoloaderNamespaces.0 = "Fam"
you might need something like
autoloaderNamespaces[] = "User_"
resources.frontController.plugins.UserAuth = "User_Plugin_Auth"
I'm currenly working on the project where i need something orther than bundle. Something i call "Module".
It should be different from the bundle in that when project is starting system doesn't know which "Modules" will be used
and how they will be configured.
Also i'm going to use these modules similar to bundles
$response = $this->forward('AcmeHelloModule:Hello:fancy');
OR
$response = $this->forward('Acme/Hello:Hello:fancy');
Here HelloController->fancyAction(); would be executed. And this controller described say in file /src/modules/Acme/Hello/Controller/HelloController.php
So the question is how to implement this ?
a solution would be to implement a PluginBundle that can dynamicly install, load and run your so called modules.
the PluginBundle would not contain specific plugin code at all, but the runtime environment for you modules/plugins. you may then save in the database which plugins/modules are enabled and load them dynamicly at runtime.
with this sollution it should be possible to create a dynamic plugin mechanism as in wordpress. modifying the AppKernel at runtime is not a good solution because you'd also have to clear the cache when en- disabeling bundles.
In AppKernel add the following method:
public function getBundle($name, $first = true)
{
if (substr($name, -6) == 'Module')) {
return $this->getBundle('ModuleBundle')->getModule($name, $first);
}
return parent::getBundle($name, $first);
}
and all the logic runs in ModuleBundle.
But make sure the type of response is the same as Kernel->getBundle();
Ok I think I am pushing my self to far here, I'm createing a project in my own MVC even I don't know what MVS is itself,
<?php
class init
{
function __construct()
{
$this->enviroment();
$this->start();
}
function enviroment()
{
/* Required Classes */
require_once( ROOTPATH . BOOTSTRAP . REDENGINE . '/class.debug.php');
require_once( ROOTPATH . BOOTSTRAP . REDENGINE . '/class.theme.php');
require_once( ROOTPATH . BOOTSTRAP . REDENGINE . '/class.url.php');
require_once( ROOTPATH . BOOTSTRAP . REDENGINE . '/language/class.lang.php');
require_once( ROOTPATH . BOOTSTRAP . REDENGINE . '/class.sessions.php');
}
function start()
{
/* Start Classes */
$uSys = new Urlsystem;
$fragments = $uSys->getFragments();
$tSys = new ThemeSystem;
$lSys = new LanguageSystem;
$sSys = new Sessions;
/* defineing APPVIEWS & APPCONTROLLER */
define( 'APPVIEWS', '/appviews' );
define( 'APPCONTROLLER', '/appcontroller' );
if ( empty($fragments) )
{
require_once( ROOTPATH . APPCONTROLLER . '/app.home.php'); /* default app controller page */
require_once( ROOTPATH . APPVIEWS . '/view.home.php'); /* default app views page */
}
if ( !empty($fragments) )
{
// Start ENGINE
if ( !file_exists(ROOTPATH . APPCONTROLLER . '/app' . $fragments . '.php') &&
!file_exists(ROOTPATH . APPVIEWS . '/view' . $fragments. '.php')
) {
if ( file_exists(ROOTPATH . APPCONTROLLER . '/app.404.php') &&
file_exists(ROOTPATH . APPVIEWS . '/view.404.php')
) {
require ROOTPATH . APPCONTROLLER . '/app.404.php';
require ROOTPATH . APPVIEWS . '/view.404.php';
}
else {
echo "NO 404 APP || VIEW";
}
}
if ( file_exists(ROOTPATH . APPCONTROLLER . '/app' . $fragments . '.php') )
{
require ROOTPATH . APPCONTROLLER . '/app' . $fragments . '.php'; // load application
if ( file_exists(ROOTPATH . APPVIEWS . '/view' . $fragments . '.php') ) {
require ROOTPATH . APPVIEWS . '/view' . $fragments . '.php';// load view
}
}
// End ENGINE
}
}
} ?>
Ok, as you see my front controller, so I know it's failing, I just notice my fail after I nearly finish my project, especialy when I need to do www.someurl.com/?$getuser or www.someurl.com/myname or a user.
Anyway my question is when do we really need MVC for PHP?
I'm looking at facebook, etc
They still use the ?this=blabla get so they are not MVC , is that right? Anyway I'm still confused how facebook does www.facebook.com/myname without it. ( htaccess ? )
If they don't use MVC then when do we really need it?
Note :
I've read many thread about when use MVC, but I haven't found one in my problem, if there is please leave a comment so I can read :)
Thanks a lot.
I believe you are confused between MVC and having RESTful URL scheme (http://en.wikipedia.org/wiki/Representational_State_Transfer#RESTful_web_services).
MVC is just a coding pattern that separates entities and modules. Like Application Logic from GUI Templates while The URL pattern is a different thing altogether. URLs are just to access a Web Resource. MVC frameworks like CodeIgnitor can still give you 'ugly' URLs if you do not work it out with .htaccess
MVC is an architectural pattern focused on separation of concerns; the URL's have nothing to do with it.
URL's are handled by the server. If it's Apache you are using, set up mod_rewrite.
That being said, you might want not to reinvent the wheel, but look at options available out there, there are plenty of MVC-oriented PHP frameworks. Find one that you like and can be productive in and use it.
I think you're confusing MVC with query params. The two are not necessarily linked, though it is true that the more popular PHP MVC frameworks do mask the params by using mod_rewrite or an equivalent method.
MVC is simply a way to keep your presentation logic separate from your business logic. Think of it this way: if you have a site using MVC, you can easily create a mobile phone version by simply changing the views based on the browser, your site's logic doesn't need to change, just the HTML that is sent to the client.
It seems that your question is confusing two different topics together. Model View Controller(MVC) and pretty URLs.
Model View Controller is a design paradigm which allows you to separate your logic(model), your templates(views), and your directing input/output(controller).
Pretty URLs on the other hand, allow for redirecting urls based on format rules(typically .htaccess rules).
Model-View-Controller - design paradigm information.
Pretty URLs tutorial - implementation of using Apache's mod_rewrite.
mod_rewrite - information on what a rewrite engine is.
Saving your code, HTML, and data in different folders is the most basic way of structuring your application - and that's the primary reason for implementing MVC: organization.
The other design patterns present in most frameworks supplement MVC, and they promote code reuse, rapid development, etc. But you can do the latter even without MVC - all it takes is a code library! Most frameworks utilize the MVC design pattern, but MVC != frameworks.
Some frameworks require you to tweak Apache (Lighty, Nginx, etc) to make it a framework extension. Pretty URLs are just a way of presenting input data (view) that are consumed by the controller so the latter can route to the appropriate handler. Seen in this light, .htaccess is an integral part of MVC for such frameworks.
Before you plunge deeper into your project, it helps to do a little more research. Most frameworks have taken the convoluted approach to MVC, which have led many to confusion. Fat-Free Framework uses a more direct and easier-to-follow path.
you might consider starting by using one of the many different MVC frameworks out there, such as CodeIgniter or cakePHP. These frameworks have bene developed by many pople and refined over a period of time. MVC is not necessary, but once the boilerplate is established, creating web applications is very fast.
When to use... All the time, is a good practice.
Personally, my choice: Symphone and Doctrine can easier to write big applications by team. But began with CodeIgniter.
You are really making it hard on yourself by attempting to writing your own mvc. (You are not doing it by manipulating the URL scheme). While it is a good educational experience to write one yourself, you will not get the level of quality and the benefits of the MVC pattern by reinventing the wheel.
Write your app in symfony, zend, codeigniter cake or any of the good open source MVC frameworks out there. When you get a feel for how it should work, then you should create your own for fun.
These frameworks exist to make your projects faster to code and more maintainable.