I am creating a website using the MVC structure. Below is a code I have used to use clean URLS and load the appropriate files. However it only works for the first level.
Say I wanted to visit mywebsite.com/admin it would work, however mywebsite.com/admin/dashboard would not. The problem is in the arrays, how could I get the array to load content after the 2nd level along with the second level.
Would it be best to create an array like this?
Array
- controller
- view
- dashboard
Any help here would be great. Also as a side question. What would be the best way to set up "custom" urls. So if I were to put in mywebsite.com/announcement it would check to see if its got controllers, failing that, check to see if it's got custom content (maybe a file of the same name in "customs" folder, and then if there's nothing execute the 404 page not found stuff) This isn't a priority question though, but loosely associated in how the code works so I thought it best to add.
function hook() {
$params = parse_params();
$url = $_SERVER['REQUEST_URI'];
$url = str_replace('?'.$_SERVER['QUERY_STRING'], '', $url);
$urlArray = array();
$urlArray = explode("/",$url);
var_dump($urlArray);
if (isset($urlArray[2]) & !empty($urlArray[2])) {
$route['controller'] = $urlArray[2];
} else {
$route['controller'] = 'front'; // Default Action
}
if (isset($urlArray[3]) & !empty($urlArray[3])) {
$route['view'] = $urlArray[3];
} else {
$route['view'] = 'index'; // Default Action
}
include(CONTROLLER_PATH.$route['controller'].'.php');
include(VIEW_PATH.$route['controller'].DS.$route['view'].'.php');
var_dump($route['controller']);
var_dump($route['view']);
var_dump($urlArray);
var_dump($params);
// reseting messages
$_SESSION['flash']['notice'] = '';
$_SESSION['flash']['warning'] = '';
}
// Return form array
function parse_params() {
$params = array();
if(!empty($_POST)) {
$params = array_merge($params, $_POST);
}
if(!empty($_GET)) {
$params = array_merge($params, $_GET);
}
return $params;
}
Can you clarify this: "The problem is in the arrays, how could I get the array to load content after the 2nd level along with the second level."
I don't understand how you want this thing to work. I checked your code and it works. Maybe you just need to put $urlArray[1] instead of $urlArray[2] and 2 instead of 3? First element in the array is at index 0.
Usually it's done like this:
Url format:
/controller/action/param1/param2/...
-controller- should be a class. That class has a method/function called -action-.
ex. /shoes/show/121/ --> this will load controller shoes
and execute the method/function show(121)
that will show the shoes that have the id 121 in the
database.
ex. /shoes/list/sport --> this will load controller shoes
and execute function list('sport') that will list all
shoes in the sport category.
As you can see, you only load one controller and from that controller you run only one function and that function will get the rest of the path and use it as parameters.
If you want to have multiple controllers for one URL, then the rest of the controllers will have to be loaded from the main controller. Most MVCs (like CodeIgniter) load only one controller per URL.
Second question:
Best way for pretty urls would be to save them in the db. This means you can have URLs like this:
/I-can-write-anything-here-No-need-to-add-ids-or-controller-names
Then you take this URL and search it in db and get the -controller- and -action- that you need for this URL.
But I have yet to see a popular MVC framework do this. I guess the reason is that the db will get a lot of queries for text matches and that will slow things down.
Popular MVC frameworks use:
/controller/action/param1/param2
This has the benefit that you can directly find the controller/action from the url.
The downside is that you will get urls like:
/shoes/list/sport
//when what you really want is
/shoes/sport
//or just
/sport //if the website only sells shoes
This can be fixed by redirecting /shoes/sport to /shoes/list/sport
If you make your own MVC then you should use OOP because if not, thing will get ugly quick: all actions/functions are in the same namespace.
Personally I would recommend that you use one of the many PHP frameworks that exist as that will take care of the routing for you and let you concentrate on writing your application. CakePHP is one that I've used for a while and it makes my life so much easier.
What I do:
I create a .htaccess file that redirects an url like www.example.com/url/path/or/something to www.example.com/index.php?url=url/path/or/something, so it will be pretty easy to do an explode on your $_GET['url']
Second, it's better because everything a user input, will be redirected to your index.php, so you have FULL control over EVERYTHING.
If you want I can PM you the url to my mvc (bitbucket) so you can have a look on how I do this ;)
(Sorry for the others, but I don't like to put url's to my site in public)
edit:
To be more precise to your particular question; It will solve your problem, because everything goes to index.php and you have full control over the requested url.
Related
One solution to automatically building navigation for a site is by scanning a folder for documents like this:
foreach(glob('pages/*.pg.php') as $_SITE_NAV_filePath):
$_SITE_NAV_filePath = explode('.pg',pathinfo($_SITE_NAV_filePath,PATHINFO_FILENAME));
$_SITE_NAV_fileName = $_SITE_NAV_filePath[0];
$_SITE_NAV_qv = preg_replace('/([A-Z])/','-$1',$_SITE_NAV_fileName); $_SITE_NAV_qv = trim($_SITE_NAV_qv,'-');
$_SITE_NAV_name = preg_replace('/([A-Z])/',' $1',$_SITE_NAV_fileName);
?>
<li><?=$_SITE_NAV_name?></li>
<?php
endforeach;
This code will turn "AnAwesomePage.pg.php" into a menu item like this :
<li>An Awesome Page</li>
This might be bad practice (?).
Anyway; I don't use this method very often since most of the time the sites have a database, and with that comes better solutions...
But my question is this:
Is there a way to prefix the filename with a integer followed by and underscore (3_AnAwesomePage.pg.php), for sorting order purposes, and pass it somehow to the destination page outside of the querystring and without any async javascript?
I could just explode the filename once again on "_" to get the sort order and store it somewhere, somehow?
This is the code for handeling the page query request:
$_SITE_PAGE['qv'] = $_GET['page'];
if (empty($_SITE_PAGE['qv'])){ $_SITE_PAGE['qv'] = explode('-','Home'); }
else { $_SITE_PAGE['qv'] = explode('-',$_GET['page']); }
$_SITE_PAGE['file'] = 'pages/'.implode($_SITE_PAGE['qv']).'.pg.php';
This code turns "An-Awesome-Page" back into "AnAwesomePage.pg.php" so it's possible to include it with php.
But with a prefix, it's not so easy.
The probliem is; Now there's no way to know what prefix number there was before since it has been stripped away from the query string. So I need to send it somehow along in the "background".
One very bad solution I came up with was to transform the navigation link into a form button and just _POST the prefix interger along with the form. At fist it sounded like a nice solution, but then I realized that once a user refreshes their page, it didn't look very good. And after all, that's not what forms are for either...
Any good solutions out there?
Or some other and better way for dealing with this?
There are two ways to keep that number saved, you can use cookies or php session variables.
But in this case, if user first enter the url in the browser or in a new browser, then he should be taken to default number.
Like you have:
1_first-page.php
2_first-page.php
3_first-page.php
If user enter the url like: domain.com/?page=first-page, you have to take him to 1_first-page.php to any number which you want to be default.
I have small problem.
I've coded a full website in php using CodeIgniter framework. One of my modules is search module, it contains text input with keyword and three select lists with filtering criterias.
That's ok, when I'm searching something - result's listing pagination is done via URL like that:
mysite.com/$keyword/$criteria1/$criteria2/$criteria3/$offset
works like a charm.
But when I'm entering into one of my images (it's image gallery) I want to have an option to go into NEXT and PREVIOUS image from my search results - the ones which I entered this image from.
I'm solving this case now in this way - I have session table called 'search_conditions' and I'm storing values of keyword and my three criterias there, but that's quite not comfortable, because why if someone opens second window and search something else there?
Then all of his searches in another windows or tabs are getting the same criteria - because with every new search, user overwrite the session value.
My next and previous functions:
public function next($count)
{
$search = $this->session->userdata('search_conditions'); //getting session table and overwriting it
$catid = isset($search['catid'])?$search['catid']:'0';
$brandid = isset($search['brandid'])?$search['brandid']:'0';
$prodid = isset($search['prodid'])?$search['prodid']:'0';
$keyword = isset($search['keyword'])?$search['keyword']:'';
$res = $this->search_model->main_search($keyword, $catid, $brandid, $prodid, $count, 1);
}
public function previous($count)
{
$search = $this->session->userdata('search_conditions');
$catid = isset($search['catid'])?$search['catid']:'0';
$brandid = isset($search['brandid'])?$search['brandid']:'0';
$prodid = isset($search['prodid'])?$search['prodid']:'0';
$keyword = isset($search['keyword'])?$search['keyword']:'';
$res = $this->search_model->main_search($keyword, $catid, $brandid, $prodid, $count-2, 1);
}
Can you recommend me some other, more comfortable solution, because this seems not to be good...
: )
Thank you!
Add an index to the $search_conditions variable:
$search_conditions[1]['catid']
$search_conditions[1]['brandid']
...
then refer to it with a controller's or config variable. This way you can allow one session to store multiple search conditions.
But I would recommend you drop storing the search condition in session. Instead, just pass it with the URI. Session data, in the case you describe, work as an intermediary; you don't need it. Use the Pagination Class and pass the search page number, not the direction (next or previous) to the URI.
Do not worry that the URI may look ugly - it only depends on what user searches for, and it's still friendly to share. Your only concern is if the GET string does not extend the limited length.
Pull the segments from the URI in your next() and previous() functions. Use the codeigniter URL helper. That should allow you to pass the different search criterion as variables to the next page, this would also remove your need to use the session.
I'm doing a website. There's a pagination, you click on links and they take you to the page you need, the links pass $_GET variable ( a href="?pn=2" ) and that works fine.
However when i add the category links (also contain $_GET variable
(a href="?sort=english") on the same page, which kind of sort the content on the page, and click it, the system simply overrides the url and deletes all the previous $_GET's.
For example, I'm on page 2 (http://website.com/index.php?pn=2)
and then I click this sorting link and what I'm expecting to get is this (http://website.com/index.php?pn=2&sort=english), but what I get is this:
(http://website.com/index.php?sort=english). It simply overrides the previous $_GET, instead of adding to it!
A relative URI consisting of just a query string will replace the entire existing query string. There is no way to write a URL that will add to an existing query. You have to write the complete query string that you want.
You can maintain the existing string by adding it explicitly:
href="?foo=<?php echo htmlspecialchars($_GET['foo']); ?>&bar=123"
Try using this:
$_SERVER['REQUEST_URI'];
On this link you can see examples. And on this link I have uploaded test document where you can try it yourself, it just prints out this line from above.
EDIT: Although this can help you get the current parameters in URL, I think it's not solution for you. Like Quentin said, you will have to write full link manually and maintain each parameter.
You could create a function that will iterate through your $_GET array and create a query string. Then all you would have to do is change your $_GET array and generate this query string.
Pseudocode (slash I don't really know PHP but here's a good example you should be able to follow):
function create_query_string($array) {
$kvps = array();
for ($key in $array) {
array_push($kvps, "$key=$array[$key]");
}
return "?" . implode("&", $kvps);
}
Usage:
$_GET["sort"] = "english";
$query_string = create_query_string($_GET);
You need to maintain the query parameters when you create the new links. The links on the page should be something like this:
Sort by English
The HTTP protocol is stateless -- it doesn't remember the past. You have to remind it of what the previous HTTP parameters were via PHP or other methods (cookies, etc). In your case, you need to remind it what the current page number is, as in the example above.
I have a social network that allows users to write blogs and ask questions. I am wanting to create dynamic URLs that post the title of the blog or question on the end of the URL via PHP.
Example:
www.blah.com/the_title_here
Looking for the cleanest most efficient way to accomplish this.
You would usually store the URL-friendly "slug" in the database row, and then have a PHP script that finds posts matching that slug.
For example, if you have a script called index.php that took a parameter called slug...
<?php
if (isset($_GET['slug'])) {
$sql = "SELECT * FROM `your_table` WHERE slug = ? LIMIT 1";
$smt = $pdo->prepare($sql);
$smt->execute(array($_GET['slug']));
$row = $smt->fetchObject();
// do something with the matching record here...
}
else {
// display home page
}
...You could then re-write requests using .htaccess:
RewriteEngine on
RewriteRule ^(.+)$ index.php?slug=$1
Using the database to do this would be sad :(
There may be many cases where you do not need to lookup the database and you will with this method. eg:- www.blah.com/signup (no point here). And db connections eats up resources, serious resources...
RewriteEngine on
RewriteRule ^(.+)$ index.php?slug=$1
as shown by martin gets you the path or slug.
Most frameworks use filesystem to achieve cleaner URLs.
One folder to hold all files and
something which is similar in theory to
<?php
$default = "home";
//function to make sure the slug is clean i.e. doesnot contain ../ or something
if(isset($_GET['slug'])) $slug = clean($_GET['slug']);
if(!isset($slug)) $slug = $default;
$files = explode('/',$slug);// or any other function according to your choice
$file = "./commands/".$files[0].".php";
if(file_exists($file))
require_once($file);
else
require_once("./commands/".$default.".php");
You can make this as simple to as complicated as you want. You can even use the database to determine the default case like what Martin did, but that should be in the $default and not the first logic you use...
Advantages of doing it this way
It is way faster than querying the database
You can scale this a lot. Vertically eg: site.com/users/piyushmishra and site.com/forums/mykickassforum or even on deeper levels like site.com/category/category-name/post-name/comments/page-3
You can setup libraries and packages easier.Scaling horizontally (add more directories to check and each directory can have one/more modules setup) eg : ./ACLcommands/users.php , ./XMLRPC/ping.php
There are lots of open source software that do this, you can look at WordPress.org or MediaWiki.org to do this. You'll need a combination of .htaccess or Apache configuration settings to add mod_rewrite rules to them.
Next, you'll want a controller file as Martin Bean wrote to look up the post... but make sure you escape/sanitize/validate input properly, otherwise you can be vulnerable to SQL injection or XSS if you have JavaScript on your site.
So it's better to use the id method and only use the slug for pretty-url purposes. WordPress.org software also suggests that going only by the slug makes it slow once you have a lot of posts. So, you can use a combination of www.blah.com/slug-phrase-goes-before-the-numeric_id and write a RegExp to match: .*(\d+)$
"www.blah.com/$id/".preg_replace('/^[a-z-]+/','',preg_replace('/[ ,;.]+/','-',strtolower($title)))
and use only $id
from title
"How do I create dynamic URLs?"
it creates url
www.blah.com/15/how-do-i-create-dynamic-urls
I have created a small search and filter form with a POST action in controller/index, which POSTs to itself the conditions and fields to paginate ($this->paginate($conditions)).
That is good for the first page, however on the subsequent pages the filter conditions are lost. Pagination passedArgs supports GET variables well.
Are there an un-complex ways to pass the POST conditions to the other paginated pages?
The method I have looked at is to pass the $conditions via the session, which isn't without complexity of assigning the session and unsetting the session on submitting the form again (more refinements to the filter criteria by the user).
The other method is passing the $conditions as serialized string with url_encode as a GET parameter.
Is there a good 'cake' way to do this more like passedArgs. Sessions and url_encode do not look like cake style.
Thanks
Is there an un complex way to pass the
post conditions to the other paginated
pages?
Nope.
Is there an good cake way to do this
more like passArgs, sessions and url
encode do not look like cake style.
There is only one way, no matter, cake or not cake.
Search must be done using GET method.
Parameters being passed via QUERY STRING.
So, make your search form with method="GET" and then use http_build_query() to assemble a query string and use it to make links to other pages.
Being a little curious, you can see an example right here on SO:
http://stackoverflow.com/questions/tagged?tagnames=php&page=5&sort=newest&pagesize=50
You can use passedArgs.
in the method controller :
if ( empty($this->passedArgs['search']) ){
$this->passedArgs['search'] = $this->data['YourModel']['search'];
}
if ( empty($this->data) ){
$this->data['YourModel']['search'] = $this->passedArgs['search'];
}
in your view :
$this->Paginator->options(array('url' => $this->passedArgs));
If it was me I would run with your idea of saving the stuff into the session. Then I would add a page dimension to the session, to store each page, thus allowing users to go back and forth with ease.
$this->Session->write('Search.page.1.params',$params);
$this->Session->write('Search.page.2.params',$params2);
In order to do it in a Cake way, you'd probably want to write your own Pagination helper, or plugin. Which you could then use more effectivly in your controllers as
$this->MyPages->paginate('MyModel');
I suppose, this functionality would also give you the option to allow your users to 'Save my search' if they wanted to, as you could dump the session params into a SavedSearch model or similar.
Don't forget to $this->Session->destroy() before starting a new search though!
You can also use the Post/Redirect/Get design pattern pattern to solve this, allowing users to bookmark URLs of searches (without them expiring as a session would) and keeping URLs looking friendly. For example:
function index($searchTerms = null) {
// post/redirect/get
if (isset($this->data['SearchForm'])) {
$this->redirect(array($this->data['SearchForm']['search_terms']));
}
// your normal code here.
}
The search form data POSTs to /controller/action but the user is redirected and instead GETs /controller/action/search+terms where the terms are passed into the action as a parameter (ie. $searchTerms).
If you simply change the form submission method to GET you will instead see something like: /controller/action?data[SearchForm][search_terms]=search+terms
Thanks Deizel for the POST / REDIRECT / GET pattern.
I implemented the GET method of posting data.
For pagination used this
$urlparams = $this->params['url'];unset($urlparams['url']);
$paginator->options(array('url' => array('?' => http_build_query($urlparams))));
We had multi checkboxes and the naming convention used where :
foreach ($checkboxes as $chbox ) {
// used variable variables to generate the data grid table based on selected fields
${'_field'.$chbox} = (isset($this->params['url']['displayfields'][$chbox])?$this->params['url']['displayfields'][$chbox]:1);
$options = array('label'=>$chbox,'type'=>'checkbox','value'=> ${'_field'.$chbox});
if ( ${'_field'.$chbox} ) $options['checked'] = 'checked';
echo $form->input('Filter.displayfields['.$chbox.']',$options);
In the post method the naming convention for the checkboxs would be Filter.displayfields.checkbox name
This helps in getting an array either in $this->data or $this->params['url']
There should be an persistent pagination plugin/component for cakePHP would make life much more easier and fun to develop with cakePHP.
Thanks all.