OK, so I'm trying to teach myself the CakePHP framework, and I'm trying to knock up a simple demo app for myself.
I have the controllers, views and models all set up and working, but I want to do something slightly more than the basic online help shows.
I have a guitars_controller.php file as follows...
<?php
class GuitarsController extends AppController {
var $name = 'Guitars';
function index() {
$this->set('Guitars', $this->Guitar->findAll());
$this->pageTitle = "All Guitars";
}
function view($id = null) {
$this->Guitar->id = $id;
$this->set('guitar', $this->Guitar->read());
// Want to set the title here.
}
}
?>
The 'Guitar' object contains an attribute called 'Name', and I'd like to be able to set that as the pageTitle for the individual page views.
Can anyone point out how I'd do that, please?
NB: I know that there is general disagreement about where in the application to set this kind of data, but to me, it is data related.
These actions are model agnostic so can be put in your app/app_controller.php file
<?php
class AppController extends Controller {
function index() {
$this->set(Inflector::variable($this->name), $this->{$this->modelClass}->findAll());
$this->pageTitle = 'All '.Inflector::humanize($this->name);
}
function view($id = null) {
$data = $this->{$this->modelClass}->findById($id);
$this->set(Inflector::variable($this->modelClass), $data);
$this->pageTitle = $data[$this->modelClass][$this->{$this->modelClass}->displayField];
}
}
?>
Pointing your browser to /guitars will invoke your guitars controller index action, which doesn't exist so the one in AppController (which GuitarsController inherits from) will be run. Same for the view action. This will also work for your DrumsController, KeyboardsController etc etc.
You can set this in the controller:
function view($id = null) {
$guitar = $this->Guitar->read(null, $id);
$this->set('guitar', $guitar);
$this->pageTitle = $guitar['Guitar']['name'];
}
Or in the view:
<? $this->pageTitle = $guitar['Guitar']['name']; ?>
The value set in the view will override any value that may have already been set in the controller.
For security, you must ensure that your layout / view that displays the pageTitle html-encodes this arbitrary data to avoid injection attacks and broken html
<?php echo h( $title_for_layout ); ?>
In response to your own answer about oo paradigm. Its like this :
function view($id) {
$this->Guitar->id = $id;
$this->Guitar->read();
$this->pageTitle = $this->Guitar->data['Guitar']['name'];
$this->set('data', $this->Guitar->data);
}
By the way, you should check if id is set and valid etc, since this is url user input.
As of CakePHP 1.3, setting page title has been changed.
$this->pageTitle = "Title"; //deprecated
$this->set("title_for_layout",Inflector::humanize($this->name)); // new way of setting title
Note: More about Inflector: http://api13.cakephp.org/class/inflector#method-Inflector
$this->pageTitle = $this->Guitar->Name;
It should go in the View though, I don't PHP, or cakePHP, but thats something a view should do, not the controller.
It should go in the controller. See this
"but I want to do something slightly more than the basic online help shows."
Isn't that always the rub? So much documentation is geared towards a bare minimum that it really does not help much. You can complete many of the tutorials available but as soon as you take 1 step off the reservation the confusion sets in. Well, it's either bare minimum or pro developer maximum but rarely hits that sweet spot of ease, clarity and depth.
I'm currently rewriting some Zend Framework documentation for my own use simply so I can smooth out the inconsistencies, clarify glossed over assumptions and get at the core, "best practice" way of understanding it. My mantra: Ease, clarity, depth. Ease, clarity, depth.
Ah, the none-obvious answer is as follows...
$this->pageTitle = $this->viewVars['guitar']['Guitar']['Name'];
I found this by placing the following code in the controller (was a long shot that paid off, to be honest)
echo "<pre>"; print_r($this);echo "</pre>";
Thanks to all those that tried to help.
echo "<pre>"; print_r($this);echo "</pre>";
how about
pr( $this );
OK, I really want to set the page title in the controller instead in the view. So here's what I did:
class CustomersController extends AppController {
var $name = 'Customers';
function beforeFilter() {
parent::beforeFilter();
$this->set('menu',$this->name);
switch ($this->action) {
case 'index':
$this->title = 'List Customer';
break;
case 'view':
$this->title = 'View Customer';
break;
case 'edit':
$this->title = 'Edit Customer';
break;
case 'add':
$this->title = 'Add New Customer';
break;
default:
$title = 'Welcome to '.$name;
break;
}
$this->set('title',$this->title);
}
The trick is that you can't set $this->title inside any action, it won't work. It seems to me that the web page reaches action after rendering, however you can do it in beforeFilter.
Related
I started working on a platform on CodeIgniter thanks to my work. The platform was started since before, so i just took the project. The thing is that i hadn't work with CI, so i just had a fast tutorial, and started developing based on how the platform was built. Now, to start, i decided to make a new page to to put a list of objects and understand a little how the PHP communicated with the HTML. The problem is that, when i go to the URL that i defined on routes.php, it gives an error "Requested resource does not exist", and i don't know what i'm doing wrong, while something similar works on other modules of the platform.
The files i'm using are:
list.php
// stuff
<?php if (has_access('agroindustrias')): ?>
<li class="<?php get_li_class('agroindustrias', $active); ?>" >
Agroindustrias
</li>
<?php endif ?>
// stuff
agroindustria.php
<?php
if (!defined('BASEPATH')) {
exit('No direct script access allowed');
}
class Agroindustria extends MY_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('agroindustria_model', 'agroindustria');
}
public function index($offset = 0)
{
$data['links'] = $this->paginate($this->agroindustria, 'agroindustrias', $offset);
$data['permisos'] = $this->getPermissions('usuario');
$data['active'] = 'usuarios';
$data['agroindustrias'] = $this->agroindustria->limit($this->limit, $offset)->get_all();
$data['submenu'] = $this->load->view('administracion/menu', $data, true);
$this->template->write_view('content', 'administracion/agroindustrias/list', $data);
$this->template->render();
}
}
routes.php(Updated with all the file, only the one before "reportes" doesn't work)
$route['administracion/agroindustria'] = 'agroindustria/index';
My URL is: http://localhost/work/administracion/agroindustria
The other controllers works with something similar, reason that i don't know what i'm doing wrong, if i still need to add something to another file, or if something that i wrote is wrong. Thanks in advance.
After the things that #Antony told me to check, i was able to find the error, it wasn't actually an error, just that the people before worked very much in the platform, that i did not undertand the mothodology to add new modules to the platform. All that i needed to do was register the new module, and the platform added the new URL as i wanted. Thanks Antony for your help!
1st of all rename agroindustria.php to Agroindustria.php if 1st letter not in upper case
if you are using method like this
public function index($offset = 0){
....
....
....
}
then you need to change routes like
$route['administracion/agroindustria/(:num)'] = 'agroindustria/index/$1';
and url
http://localhost/work/administracion/agroindustria/0
The best solution is change in your controller
//if offset not change
public function index(){
$offset = 0;
....
....
....
}
//if your offset depend upon 3rd segments of URL then
public function index(){
$offset = if($this->uri->segment(3)) ? $this->uri->segment(3) : 0;
....
....
....
}
For which cases are these classes suitable? I've been trying to use both, but none of them works.
The component skeleton was generated, and there are CRUD operations in the administrator's side. I tried using JToolbarHelper from this generated code, like this in mycomponent/view.html.php:
// Overwriting JView display method
function display($tpl = null)
{
// Include helper submenu
InvoiceHelper::addSubmenu('invoice');
// Assign data to the view
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
// Check for errors.
if (count($errors = $this->get('Errors'))){
JError::raiseError(500, implode('<br />', $errors));
return false;
};
// Set the toolbar
$this->addToolBar();
// Show sidebar
$this->sidebar = JHtmlSidebar::render();
// Display the view
parent::display($tpl);
}
protected function addToolBar()
{
JLoader::register('JToolbarHelper', JPATH_ADMINISTRATOR.'/includes/toolbar.php');
$canDo = InvoiceHelper::getActions();
JToolBarHelper::title(JText::_('Invoice Manager'), 'invoice');
if($canDo->get('core.create')){
JToolBarHelper::addNew('invoic.add', 'JTOOLBAR_NEW');
};
if($canDo->get('core.edit')){
JToolBarHelper::editList('invoic.edit', 'JTOOLBAR_EDIT');
};
if($canDo->get('core.delete')){
JToolBarHelper::deleteList('', 'invoice.delete', 'JTOOLBAR_DELETE');
};
}
But it doesn't even appear on the page.
Then I came across this tutorial http://docs.joomla.org/J3.x:Using_the_JToolBar_class_in_the_frontend and it kindof works, except I can't imagine implementing something like a list of entities with checkboxes and operations for each. And it's unclear for me how to handle form submissions using this approach, seems like it happens through JS, do I get it right?
So, please tell, what's the difference and why doesn't the first approach even make the toolbar appear?
I know this is a long time ago, but I was looking to achieve the same result and found the following to loads the toolbar on the page. Using the above code:
protected function addToolBar()
{
JLoader::register('JToolbarHelper', JPATH_ADMINISTRATOR.'/includes/toolbar.php');
$canDo = InvoiceHelper::getActions();
JToolBarHelper::title(JText::_('Invoice Manager'), 'invoice');
if($canDo->get('core.create')){
JToolBarHelper::addNew('invoic.add', 'JTOOLBAR_NEW');
}
if($canDo->get('core.edit')){
JToolBarHelper::editList('invoic.edit', 'JTOOLBAR_EDIT');
}
if($canDo->get('core.delete')){
JToolBarHelper::deleteList('', 'invoice.delete', 'JTOOLBAR_DELETE');
}
$this->toolbar = JToolbar::getInstance(); // <<<---------- ADD THIS TO METHOD!
}
Then in your view do this:
<?php echo $this->toolbar->render(); ?>
Hope this helps!!! enjoy.
I'm trying to figure out how to use one of my view elements inside of a controller...
I know, I know: "Don't do that!" (99% of the time this is the correct answer)
But I think I actually have a good reason. The action is handling an AJAX request which returns markup. The returned markup is a list which I display everywhere else using an element. So in an effort to keep my code DRY, I think it's appropriate to do this here.
Is this possible?
Easy:
$view = new View($this, false);
$content = $view->element('my-element', $params);
Also:
DON'T DO THAT ANYMORE!!!
Sometimes, you need to render a CakePhp element from a view and inject its content into the page using AJAX the same time. In this case rendering element as a regular view from controller is better than creating a dedicated view that just contains <?php echo $this->element('some_element') ?>, and may be done this way:
<?php
public function ajax_action() {
// set data used in the element
$this->set('data', array('a'=>123, 'b'=>456, 'd'=>678));
// disable layout template
$this->layout = 'ajax';
// render!
$this->render('/Elements/some_element');
}
I know this is an old question and other people have already given basically the same answer, but I want to point out that this approach (provided by Serge S.) ...
<?php
public function ajax_action() {
// set data used in the element
$this->set('data', array('a'=>123, 'b'=>456, 'd'=>678));
// disable layout template
$this->layout = 'ajax';
// render!
$this->render('/Elements/some_element');
}
...is not a hacky workaround, but is in fact the recommended approach from the CakePHP docs for this common and legitimate use case:
If $view starts with ‘/’, it is assumed to be a view or element file
relative to the /app/View folder. This allows direct rendering of
elements, very useful in AJAX calls.
(Again: Credit to Serge S. for the code above)
$this->view = '/Elements/myelement';
You should use a client-side template. You should never return mark-up from a web service or API, just data. Have your JavaScript take the data, and then format it how you wish.
For example:
function getItems() {
$.get('/some/url', function(response) {
if (response.data.length > 0) {
for (var i = 0; i < response.data.length; i++) {
var item = response.data[i];
$('.results').append('<li>' + item.title + '</li>');
}
}
});
};
This is just an example written off the cuff. Obviously you’ll need to write your own implementation.
The way I did any ajax handling in Cake was to have my own AjaxController. Any interaction of ajax-kind goes there, which in-turn uses their own views (and view partials / elements). That way you can keep your code DRY and isolate and propagate all ajax use-cases there.
Example excerpt:
<?php
class AjaxController extends AppController {
/**
* (non-PHPdoc)
* Everything going to this controller should be accessed by Ajax. End of story.
* #see Controller::beforeFilter()
*/
public function beforeFilter() {
parent::beforeFilter();
$this->autoRender = false;
$this->layout = false;
if (!$this->request->is('ajax')) {
$this->redirect('/');
}
}
public function preview() {
if ($this->request->is('ajax')) {
$this->set('data', $this->data);
$this->render('/Elements/ajaxpreview');
}
}
?>
Here's the source: https://github.com/Sobient/dosspirit/blob/master/app/Controller/AjaxController.php
Hi i wont to make something like that.
http:// example.com/ - Main Controller
http:// example.com/rules/ - Main Controller where content get from database, but if not exist
return 404 page. (It's ok, isn't problem.)
But if i have subfolder in application/controlles/rules/
I want to redirect it to Main Contorller at Rules folder.
This follow code can solve problem, but i don't know how it right realise.
At routes.php:
$route['default_controller'] = "main";
$route['404_override'] = '';
$dirtest = $route['(:any)'];
if (is_dir(APPPATH.'controllers/'.$dirtest)) {
$route['(:any)'] = $dirtest.'/$1';
} else {
$route['(:any)'] = 'main/index/$1';
}
Ok, what I have:
controllers/main.php
class Main extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('main_model');
}
public function index($method = null)
{
if (is_dir(APPPATH.'controllers/'.$method)) {
// Need re-rout to the application/controllers/$method/
} else {
if ($query = $this->main_model->get_content($method)) {
$data['content'] = $query[0]->text;
// it shows at views/main.php
} else {
show_404($method);
}
}
$data['main_content'] = 'main';
$this->load->view('includes/template', $data);
}
}
Updated Again (routes.php):
So, seem's like what i search (work example):
$route['default_controller'] = "main";
$route['404_override'] = '';
$subfolders = glob(APPPATH.'controllers/*', GLOB_ONLYDIR);
foreach ($subfolders as $folder) {
$folder = preg_replace('/application\/controllers\//', '', $folder);
$route[$folder] = $folder.'/main/index/';
$route[$folder.'/(:any)'] = $folder.'/main/$1';
}
$route['(:any)'] = 'main/index/$1';
But, in perfectly need some like this:
http:// example.com/1/2/3/4/5/6/...
Folder "controllers" has subfolder "1"?
YES: Folder "1" has subfolder "2"?
NO: Folder "1" has controller "2.php"?
NO: Controller "controllers/1/main.php" has function "2"?
YES: redirect to http:// example.com/1/2/ - where 3,4,5 - parameters..
It is realy nice, when you have structure like:
http://example.com/blog/ - recent blog's posts
http://example.com/blog/2007/ - recent from 2007 year blog's posts
http://example.com/blog/2007/06/ - same with month number
http://example.com/blog/2007/06/29/ - same with day number
http://example.com/blog/web-design/ - recent blog's post's about web design
http://example.com/blog/web-design/2007/ - blog' posts about web design from 2007 years.
http://example.com/blog/current-post-title/ - current post
Same interesting i find http://codeigniter.com/forums/viewthread/97024/#490613
I didn't thoroughly read your question, but this immediately caught my attention:
if (is_dir($path . '/' . $folder)) {
echo "$route['$folder/(:any)'] = '$folder/index/$1';"; //<---- why echo ???
}
Honestly I'm not sure why this didn't cause serious issues for you in addition to not working.
You don't want to echo the route here, that will just try to print the string to screen, it's not even interpreted as PHP this way. There are also some issues with quotes that need to be remedied so the variables can be read as variables, not strings. Try this instead:
if (is_dir($path . '/' . $folder)) {
$route[$folder.'/(:any)'] = $folder.'/index/$1';
}
Aside: I'd like to offer some additional resources that are not directly related to your problem, but should help you nonetheless with a solution:
Preferred way to remap calls to controllers: http://codeigniter.com/user_guide/general/controllers.html#remapping
Easier way to scan directories: http://php.net/manual/en/function.glob.php
It's hard to say why the registering of your routes fails. From your code I can see that you're not registering the routes (you just echo them), additionally I see that the usage of variables in strings are used inconsistently. Probably you mixed this a bit, the codeigniter documentation about routes is not precise on it either (in some minor points, their code examples are not really syntactically correct, but overall it's good).
I suggest you first move the logic to register your dynamic routes into a function of it's own. This will keep things a bit more modular and you can more easily change things and you don't pollute the global namespace with variables.
Based on this, I've refactored your code a bit. It does not mean that this works (not tested), however it might make things more clear when you read it:
function register_dynamic_routes($path, array &$route)
{
$nodes = scandir($path);
if (false === $nodes)
{
throw new InvalidArgumentException(sprintf('Path parameter invalid. scandir("$path") failed.', $path));
}
foreach ($nodes as $node)
{
if ($node === '.' or $node === '..')
continue
;
if (!is_dir("{$path}/{$node}")
continue
;
$routeDef = "{$folder}/(:any)";
$routeResolve = "{$folder}/index/\$1";
$route[$routeDef] = $routeResolve;
# FIXME debug output
echo "\$route['{$routeDef}'] = '{$routeResolve}';";
}
}
$path = APPPATH.'controllers/';
register_dynamic_routes($path, $route);
$route['(:any)'] = 'main/index/$1';
Next to this you probably might not want to shift everything onto the index action, but a dynamic action instead. Furthermore, you might want to have a base controller that is delegating everything into the sub-controllers instead of adding the routes per controller. But that's up to you. The example above is based on the directory approach you outlined in your question.
Edit: Additional information is available in the Controllers section next to the URI Routing section
All this seems kind of complicated.
Plus, if you have hundreds (or thousands or more?) of possible routes in a database you may not want to load all of them into the "$routes" array every time any page loads in your application.
Instead, why not just do this?
last line of routes.php:
$route['404_override'] = 'vanity';
Controller file: Vanity.php:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Vanity extends MY_Controller {
/**
* Vanity Page controller.
*
*/
public function __construct() {
parent::__construct();
}
public function index()
{
$url = $_SERVER['PHP_SELF'];
$url = str_replace("/index.php/", "", $url);
// you could check here if $url is valid. If not, then load 404 via:
//
// show_404();
//
// or, if it is valid then load the appropriate view, redirect, or
// whatever else it is you needed to do!
echo "Hello from page " . $url;
exit;
}
}
?>
All,
I am studying some sample code given in my web dev class as an example of MVC (again, for the web). In this code, there's a system to navigate from the index.php page to the various controllers (which then call the Model and View modules), and then back into index.php.
I understand how the MVC works.
What I'm grappling with is the navigation mechanism. I am having difficulties understanding how all the pieces work together.
Could anyone take a look at the code below and tell me if this matches a well known method / pattern to deal with dynamic website navigation? (Maybe the Front Controller?) If it does, then my hope is that I can more easily do some more research on it.
Many thanks!
JDelage
Index.php
<?php
require_once("User.php");
session_start();
if (isset($_GET['action']))
$action= $_GET['action'];
else
$action="";
switch ($action) {
case 'login':
require_once('Login.php');
$command= new LoginControler();
break;
case 'logoff':
require_once('Logoff.php');
$command= new LogoffControler();
break;
// Several other cases
default:
require_once('Unknown.php');
$command= new UnknownControle();
}
$command->execute();
require_once('EntryMenu.php'); // Those are objects that represent both the
// menu label and the links.
$menu= array(
new EntryMenu("Login", "index.php", array("action" => "logon")),
new EntryMenu("Logoff", "index.php", array("action" => "logoff")),
new EntryMenu("Write", "index.php", array("action" => "write")),
new EntryMenu("Read", "index.php", array("action" => "read"))
);
if ($command->redirect) {
header('Location: ' . $command->redirect);
} else if ($command->page) {
include("ui/header.php");
include("ui/menu.php");
echo "<div class='content'>";
include("ui/". $command->page);
echo "</div>";
include("ui/footer.php");
}
?>
Controler.php
<?php
class Controler {
public $page= "problem.php";
function execute() {}
}
?>
LogoffControler.php
<?php
require_once('Controler.php');
class LogoffControler extends Controler {
function execute() {
$this->redirect= "index.php";
unset($_SESSION['user']);
}
}
?>
LoginControler.php
<?php
require_once('LoginModel.php'); // This manages the exchanges with the user db
require_once('Controler.php');
class ConnexionControle extends Controler {
public $page= "LoginForm.php";
function execute() {
// More code to deal with incorrectly filled login forms
$login = new LoginModel();
$login->loginUser($_POST['login'], $_POST['password']);
if ($login->userLogedIn()) {
$_SESSION['user']= $login->user;
$this->redirect= "index.php";
}
// More code to deal with invalid logins
}
}
?>
I am assuming you understand the controller part, and is asking about the switch..case statements. I haven't come across an official name for that yet,but most MVC frameworks for PHP (Kohana, CakePHP, CodeIgniter, Fat Free and etc.) calls that 'routing'. It's mapping of a URL to a controller.
Using a switch..case sets of statement is one of the easier ways. More sophisticated solutions use RegEx to match pre-defined URL patterns to resolve what controller to invoke, and what are its parameters (usually bundled as a 'request')
Other methods include using URL rewriting to come up with pretty urls, such as /articles/month/nov/article-id/3
which in 'ugly url form' is :
action=articles&month=nov&article-id=3
If you would like an easy-to-dissect verion of a MVC system you could try the 1kb PHP MVC which handles everything you are attempting in a much cleaner fashion. Though you might have to break up the code if you really want to read it as it is in compressed form.
With this system you simply place a controller in /classes/controller/ named somthing.php and you can then access it from the URL like http://site.com/something.
Loading Models is also easy and doesn't require any include or require calls.
class Controller_Something
{
public function index()
{
$model = new Model_User();
}
}