PHP Routing System - own MVC - php

I am trying to develop my own MVC and i got to the point where a routing system is due.
I have used some examples from the internet and partially it works fine. Evan so, i think something is wrong and i will detail it right bellow.
My MVC has a frontend and a backend. The first question is : Do i need to load the same routing system for the frontend and the backend? Or do i need to have different routing systems? How would you choose?
In the frontend, whenever i am using the main menu to navigate i can go to home or contact page and the pages are displaying corectlly - and the browser url - looks fine : localhost/home or localhost/contact (without the php extension - which is the desired behaviour.)
In the backend section of my website - reaching it by using localhost/admin - of course the routing system from my front-end section has some rules for the admin, so when i am reaching the admin section the browser url is fine - showing http://localhost/admin/ and displays some login form. The Login Form uses post to send info to another page- login.php which runs the login system, and if successfull requires a page named home.php. The problem is after the successfull login - the browser url is http://localhost/admin/login.php !!! which IS NOT the desired behaviour because i am expecting in fact localhost/admin/home (without the php extension) . i have tried with a different routing system for the backend - but for some reason it's not taken in consideration and i have also tried to include the routing system after the succesfull login. I cannot find a good solution. I am surely missing something and i do not know why so please help me. This is how my routing system is looking.
<?php
// Include router class
include('Route.php');
// Add base route (startpage)
Route::add('/',function(){
require 'pages/home.php';
});
// Simple test route that simulates static html file
Route::add('/contact',function(){
require 'pages/contact.php';
});
// Post route example
Route::add('/contact-form',function(){
echo '<form method="post"><input type="text" name="test" /><input type="submit" value="send" /></form>';
},'get');
// Post route example
Route::add('/contact-form',function(){
echo 'Hey! The form has been sent:<br/>';
print_r($_POST);
},'post');
// Accept only numbers as parameter. Other characters will result in a 404 error
Route::add('/foo/([0-9]*)/bar',function($var1){
echo $var1.' is a great number!';
});
Route::run('/');
My router Class:
class Router
{
/**
* The request we're working with.
*
* #var string
*/
public $request;
/**
* The $routes array will contain our URI's and callbacks.
* #var array
*/
public $routes = [];
/**
* For this example, the constructor will be responsible
* for parsing the request.
*
* #param array $request
*/
public function __construct(array $request)
{
/**
* This is a very (VERY) simple example of parsing
* the request. We use the $_SERVER superglobal to
* grab the URI.
*/
$this->request = basename($request['REQUEST_URI']);
}
/**
* Add a route and callback to our $routes array.
*
* #param string $uri
* #param Callable $fn
*/
public function addRoute(string $uri, \Closure $fn) : void
{
$this->routes[$uri] = $fn;
}
/**
* Determine is the requested route exists in our
* routes array.
*
* #param string $uri
* #return boolean
*/
public function hasRoute(string $uri) : bool
{
return array_key_exists($uri, $this->routes);
}
/**
* Run the router.
*
* #return mixed
*/
public function run()
{
if($this->hasRoute($this->request)) {
$this->routes[$this->request]->call($this);
}
}
}
and my successfull login statement which redirect to home :
if ($login) {
require './pages/home.php';
} else {
echo 'ERROR';
}
my htaccess from the admin section folder :
DirectoryIndex index.php
FallbackResource /index.php
# enable apache rewrite engine
RewriteEngine on
# set your rewrite base
# Edit this in your init method too if you script lives in a subfolder
RewriteBase /admin
# Deliver the folder or file directly if it exists on the server
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Push every request to index.php
RewriteRule ^(.*)$ index.php [QSA]

Related

How to setup url rewriting for a website where dynamic links are inserted

I am trying to make website where users are able to post content using an WYSIWYG editor. In my .htaccess file i have
FallbackResource index.php
which is successfully sending all requests to the index.php file. I am then using PHP to require the desired file
$path = explode('/', $_SERVER['REQUEST_URI']);
//[SCRIPT_FILENAME] => C:/xampp/htdocs/tests/index.php
if($path[2] == 'notifications'){
require_once 'notifications.php';
}
All this works fine. The problem arises when i try to redirect to an external link like google.com. The link itself gets redirected to the index page instead of being redirected.
This is the key because users can insert tags for other websites as reference in their text with the WYSIWYG editors. So my question is, Am i doing this right or do i need a different approach? and if so, which approach?. I would greatly appreciate similar approaches to those used by sites like facebook or twitter.
AS for an approach I would use a method similar to CodeIgniter or other MVC frameworks ( but simpler ).
So first you have to devise a schema, what I mean by that is something like this
www.yoursite.com/index.php/{controller}/{method}/args ...
Then you would build a router to go parse the url. I can write you one but it will take a minute.
UPDATE
You can find it on my github page here
But for refrence here is the code:
SimpleRouter.php
/**
* A simple 1 level router
*
* URL schema is http://example.com/{controller}/{method}/{args ... }
*
* #author ArtisticPhoenix
* #package SimpleRouter
*/
class SimpleRouter{
/**
* should be the same as rewrite base in .htaccess
* #var string
*/
const REWRITE_BASE = '/MISC/Router/';
/**
* path to controller files
*
* #var string
*/
const CONTOLLER_PATH = __DIR__.'/Controllers/';
/**
* route a url to a controller
*/
public static function route(){
//normalize
if(self::REWRITE_BASE != '/'){
$uri = preg_replace('~^'.self::REWRITE_BASE.'~i', '',$_SERVER['REQUEST_URI']);
}
$uri = preg_replace('~^index\.php~i', '',$uri);
$uri = trim($uri,'/');
//empty url, like www.example.com
if(empty($uri)) $uri = 'home/index';
//empty method like www.example.com/home
if(!substr_count($uri, '/')) $uri .= '/index';
$arrPath = explode('/', $uri);
$contollerName = array_shift($arrPath);
$methodName = array_shift($arrPath);;
$contollerFile = self::CONTOLLER_PATH.$contollerName.'.php';
if(!file_exists($contollerFile)){
//send to error page
self::error404($uri);
return;
}
require_once $contollerFile;
if(!class_exists($contollerName)){
self::error404($uri);
return;
}
$Controller = new $contollerName();
if(!method_exists($Controller, $methodName)){
self::error404($uri);
return;
}
if(!count($arrPath)){
call_user_func([$Controller, $methodName]);
}else{
call_user_func_array([$Controller, $methodName], $arrPath);
}
}
/**
* call error 404
*
* #param string $uri
*/
protected static function error404($uri){
require_once self::CONTOLLER_PATH.'home.php';
$Controller = new home();
$Controller->error404($uri);
}
}
Default Controller home.php
/**
*
* The default controller
*
* #author ArtisticPhoenix
* #package SimpleRouter
*/
class home{
public function index($arg=false){
echo "<h3>".__METHOD__."</h3>";
echo "<pre>";
print_r(func_get_args());
}
public function otherpage($arg){
echo "<h3>".__METHOD__."</h3>";
echo "<pre>";
print_r(func_get_args());
}
public function error404($uri){
header('HTTP/1.0 404 Not Found');
echo "<h3>Error 404 page {$uri} not found</h3>";
}
}
2nd Controller (example) user.php
/**
*
* An example users router
*
* #author ArtisticPhoenix
* #package SimpleRouter
*/
class user{
public function index(){
echo "<h3>".__METHOD__."</h3>";
}
public function login(){
echo "<h3>".__METHOD__."</h3>";
}
}
Rewrite (remove index.php) .htaccess
RewriteEngine On
# For sub-foder installs set your RewriteBase including trailing and leading slashes
# your rewrite base will vary, possibly even being / if no sub-foder are involved
RewriteBase /MISC/Router/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
Finally index.php
require_once __DIR__.'/SimpleRouter.php';
SimpleRouter::route();
I went with a static call to the router just to keep things simple, this way no instance is needed of the class which only has 2 methods (1 public) anyway.
I built it as a single level router, what I mean by that is that you cannot put controllers in sub-folders, such as Controllers/user/home.php instead of just Controllers/user.php. This should be fine for small to medium sized sites, but for a vary large site it may be desirable to nest the controllers. However this adds a bunch of complexity to it.
The advantage of this way, instead of individually creating the routes should be obvious. You shouldn't have to touch the code as long as the route (the url) follows the simple schema I outlined. This also allows you organize the sections of your site in their own files by functionality. For example take the user.php controller. Would let you put all that functionality in one place, such as the login, profile, logout pages etc...
If you were to add a member.php controller you can put all the stuff that you only want to show to logged in users there. And then in the __construct method of that controller you check if the current session is logged in and it covers all methods for that file.
The url schema is this http://example.com/{controller}/{method}/{args ... }. But you can also use urls like this http://example.com/index.php/{controller}/{method}/{args ... }. I'm using Mod Rewrite to allow for the removal of the index.php in the URL, so if you don't have that then you have to put index.php in the URL ( or remove it by some other means )
REWRITE_BASE On my local test server I just place everything in it's own folder as I am to lazy to setup virtual hosts. So you would want to change this constant, and the matching .htaccess, value to be whatever your sub folder is ( maybe nothing ). I just left it here for example of how to use it within a sub-folder.
Lastly, this is the C in traditional MVC architecture. You should avoid doing what is called "Business logic" in the controllers, you should also avoid outputting HTML directly from the controller (although I did this in the examples). Instead of outputting the HTML, I would suggest using a template engine like Blade or Twig there are many of these freely available. Think of a Controller like the "Glue" that joins the "Model (business logic)" to the "View (template)". Business logic would be like a User class (uppercase U like a noun, not lowercase as in our user controller) that handles the Database functionality for users. So instead of putting that code right in the Controller you would build a "User Model" and import that into the controller. This is the "Right Way" to do it, but of course it adds some complexity up front, but you will find later on the organization of it to far outweigh that in time savings.
Now some examples
404 errors (and missed routes):
http://example.com/foo routes to home::error404() and outputs <h3>Error 404 page foo/index not found</h3>
http://example.com/home/foo routes to home::error404() and outputs <h3>Error 404 page home/foo not found</h3>
Default Home page:
http://example.com/ routes to home::index()
http://example.com/index.php routes to home::index()
http://example.com/home routes to home::index()
http://example.com/home/index routes to home::index()
http://example.com/home/index/hello routes to home::index('hello')
http://example.com/home/index/hello/world routes to home::index('hello','world')
As it stands you have to put the full path in for arguments for home, the above code could be changed to account for this but it has some repercussions for missing pages. Basically any missing page would become an argument of the controller. So if you had http://example.com/user/foo it would call user::index('foo') if that was done. Of course this too can be accounted for but the complexity starts to stack up.
http://example.com/home/otherpage routes to home::otherpage() but issues a warning for missing argument.
http://example.com/home/otherpage/hello routes to home::otherpage('hello')
http://example.com/index.php/home/otherpage/hello routes to home::otherpage('hello')
If you look: home::index($arg=false) defines a default, where this method does not. So this method requires an argument. The rest of this should be pretty self explanatory.
Second Controller user example:
http://example.com/user routes to user::index()
http://example.com/user/index routes to user::index()
http://example.com/user/login routes to user::login()
The rest of this is pretty much the same, I just wanted to show how to organize the controllers by functionally for the site.
I should note that this can handle an unlimited number of arguments following the {method} in the url.
Enjoy!

Symfony route to file misunderstanding

I had an intention to dynamically generate a robots.txt file for different domain names.
I removed the file from /web/ directory.
I added
bits_cb.common:
resource: "#BitsClientoboxBundle/Controller/CommonController.php"
type: annotation
to routing.yml.
I added
/**
* #Route("/robots.txt", name="robots")
* #param Request $request
*/
public function indexAction(Request $request)
{
//
}
to CommonController.php.
The debug:router console command there is route:
robots ANY ANY ANY /robots.txt
But if I try to get from http://{{sitename}}/robots.txt it returns 404.
To be sure, if I change
* #Route("/robots.txt", name="robots")
to
* #Route("/robots", name="robots")
and get from http://{{sitename}}/robots it works okay.

Laravel 5 custom 404 controller

I am making laravel app that has a CMS with pages stored in the database. A page record in the database has an id, title, content and url slug.
I want it so that if someone visits the url slug then it will load a page to display the content of that page on. So I think I need to override the 404 handler so that say the user goes to mysite.com/about before it 404's it checks the database to see if there is a record with about stored as the url slug. If there is then it will load the page to show the content, or if not then it will 404. I'm 90% of the way there with the following solution:
I have modified the render method in App\Exceptions\handler.php to be the following:
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
$page = Page::where('url', '=', Request::segment(1))->first();
if(!is_null($page)) {
return response(view('page', [ 'page' => $page ]));
}
}
return parent::render($request, $e);
}
This works however there is a problem, all of my menus are stored in the database as well and I have a made FrontendController that gets all of the menus from the database to be loaded into the views. All of my controllers extend FrontendController so that this is done on every page load and I don't have to repeat any code. So is there a way that I can route all 404s through a controller instead so that it can extend my FrontendController? Or am I just going to have to repeat all of the logic that the front end controller does in the exceptions handler file?
Solved it! Thanks to Styphon for giving me the idea, I just defined the following route as my very last route:
Route::get('/{url}', 'FrontendController#page');

Symfony2 Controller Route clash

Hey all I have a bit of a problem with root annotations in Symfony2.
I have two different controllers that call methods from the same URL positions /test.
Controller 1:
**
* #Route("/test", service="myProject.test.controller.art")
* #Cache(expires="+5 minutes", public=true)
*/
class BlogController
{
/**
* #Route("/{text}", defaults={"text" = null})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}
}
Controller 2:
**
* #Route("/test" , service="myProject.test.controller.sitemap"))
* #Cache(expires="+5 minutes", public=true)
*/
class SitemapController
{
/**
* #Route("/sitemap.xml/")
* #Method({"GET"})
*/
public function sitemapAction()
{
//..................
}
}
The problem is that the second Controller is never matched only if is add in my #route("/sitemap.xml/") but I realy want the route to be only #route("/sitemap.xml").
I think the problem is when i input the url /test/sitemap.xml Symfony treats sitemap.xml as /{text} variable route in first controller.
Can I make a exception so that first controller ends as soon as it hits sitemap.xml....?
I read something about requirements but dont quiet understand this concept
The router will use the first route that matches the path.
The only way to prioritize a route over another which could match is to ensure that the stricter requirements are check before the weaker ones.
Normally this would be accomplished by placing the sitemapAction method above listAction.
However since you have a controller for each of these, you will have to put the controllers in the correct order.
To do this you will need to add the controllers to the config individually like this:
app_sitemap:
resource: "#AppBundle/Controller/SitemapController.php"
type: annotation
prefix: /
app_blog:
resource: "#AppBundle/Controller/BlogController.php"
type: annotation
prefix: /
This way the controllers will be iterated in this order.
However it is better if you can give each route a unique path, perhaps:
#Route("/query/{text}", defaults={"text" = null})
according to documentation
Earlier Routes always Win
What this all means is that the order of the routes is very important.
If the blog_show route were placed above the blog route, the URL
/blog/2 would match blog_show instead of blog since the {slug}
parameter of blog_show has no requirements. By using proper ordering
and clever requirements, you can accomplish just about anything.
http://symfony.com/doc/current/book/routing.html
i suggest to use yml or xml file for routing
or you can make a requirement in your first route
/**
* #Route("/{text}", defaults={"text" = null}, requirements={"text" = "^(?!sitemap\.xml)$"})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}

Routes issue in laravel 4

I am new to laravel and learning it now. I am giving following Route in routes.php file
Route::resource('contacts', 'ContactsController');
But when I load my page in browser, it gives me following error
Unhandled Exception
Message:
Call to undefined method Laravel\Routing\Route::resource()
Location:
/Users/zafarsaleem/Sites/learning-laravel/application/routes.php on line 35
My complete routes.php file is below
Route::resource('contacts', 'ContactsController');
Route::get('/', function() //<------- This is line 35
{
return View::make('home.index');
});
How can I remove this error?
Edit
ContactsController code is below and I want index() function to be used
class ContactsController extends BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
Contact::all();
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$input = Input::json();
Contact::create(array(
'first_name' => $input->first_name
'last_name' => $input->last_name
'email_address' => $input->email_address
'description' => $input->description
));
}
/**
* Display the specified resource.
*
* #return Response
*/
public function show($id)
{
return Contact::find($id);
}
/**
* Show the form for editing the specified resource.
*
* #return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #return Response
*/
public function update($id)
{
$contact = Contact::find($id);
$input = Input::json();
$contact->first_name = $input->first_name;
$contact->last_name = $input->last_name;
$contact->email_address = $input->email_ddress;
$contact->description = $input->description;
$contact->save();
}
/**
* Remove the specified resource from storage.
*
* #return Response
*/
public function destroy($id)
{
return Contact::find($id)->delete();
}
}
Edit 2
I tried both following routes but ended up same below error
Route::resource('contacts', 'ContactsController', ['only', => ['index']]);
Route::get('contacts','ContactsController#index');
After reinstalling laravel 4 now I am getting following error
404 Not Found
The requested URL /contacts was not found on this server.
_____________________________________________________________________
Apache/2.2.22 (Unix) DAV/2 PHP/5.3.15 with Suhosin-Patch Server at bb.dev Port 80
Edit 3
Here is what did now, I edit "/private/etc/apache2/users/.conf" and changed from "AllowOverride None" to "AllowOverride All" and then restarted my apache server. Now I am getting following error
403 Forbidden
You don't have permission to access /contacts on this server.
__________________________________________________________________________________
Apache/2.2.22 (Unix) DAV/2 PHP/5.3.15 with Suhosin-Patch Server at bb.dev Port 80
Why don't I have permission for this contacts controller? It is making me crazy now.
Here is my .htaccess file
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
Have you tried this on another server? A lot of things can go wrong with rewriting (I've lost hours fixing the .htaccess), so the problem may be Apache and not Laravel.
This works for me in public/.htaccess:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>
And have you tried going to index.php/contacts instead of /contacts? If that works, the problem is Apache, not Laravel.
It may be a namespace issue -- it should be calling the function on Illuminate\Routing\Router but your exception refers to Laravel\Routing\Route::resource().
Is it possible that your configuration file still has references to Laravel3 in it?
Try changing your routes to
Route::resource('/contacts', 'ContactsController');
In ContactsController.php change index to return instance of the model
public function index()
{
return Contact::all();
}
I had the same $#%& issue, and after hours of searching I found it is not a problem of the .httacess file. What I just did to fix this:
composer update --dev
I think that the --dev bit is the important thing. Hope that helps someone.
ok i had the issue up to one minute ago!
it is because of ide-helper
to solve you should comment the following code in routes.php
use Illuminate\Routing\Route;

Categories