How to merge two controllers in one view.
I have two controllers:
1. PostController
2. CommentController
Post controller will show all posts from database, and that posts can have comments. For comments i use another controller CommentController to avoid DRY. In html post list while looping am trying to attach comments if exist for all post bassed on their ID.
In my PostController > indexAction() am fetch all posts
// controllers/PostController.php
/**
* List all posts
*
*/
public function index()
{
$data = array(
'posts' => $this->post->findAll(),
);
$this->load->view('post/index', $data);
}
Here is method from comment controller for listing comments assigning post_id:
// controllers/CommentController.php
/**
* List all comments assigning post id
*
* #param int $post_id
*/
public function index($post_id)
{
$data = array(
'comments' => $this->comment->findAllByPostId($post_id), // <-- SELECT * FROM comments WHERE post_id = {post_id}
);
$this->load->view('comment/index', $data);
}
So now in post/index am fetch all posts:
<?php if($posts): ?>
<?php foreach ($posts as $post): ?>
<h1> <?= $post->title; ?> </h1>
<div> <?= $post->text; ?> </div>
<div class="comment-list">
<!-- How to call here comment controller and index($post->post_id) -->
<!-- i can use load->view('comment/index') but with this i do nothin i need to assigning post id
<!-- Need somthing $commentObject->index($post->post_id) but this in MVC is not good idea -->
</div>
<?php endforeach() ;?>
<?php endif; ?>
Any other solution ?
My solution to slove this is to put all in one controller Post. But i think that is bad practice bcs i will DRY latter. I need that comment controller for other ex(PictureController can have also comments i dont want DRY)
Maybe my process or organization is bad ?
ps. i to search SO for this but that results not helpful for me
the controller gets the data from a model. so in general any database interaction is going to happen in the model, and then that controller asks "did you get my information? if yes, show it, if no, do something else" When everything is sorted out the data is sent to the view.
one controller can call from many different models, and can send more then one data structure to the view.
public function index()
{
// assuming you have a model called 'post'
// check if any posts came back from the search
if( ! $posts = $this->post->findAll() )
{
$this->_showNoPostsReturned() ;
}
// now assuming you have a model called comment
// in your model you will have to foreach through posts etc
// did any comments come back?
elseif( ! $comments = $this->comment->returnFor($posts) )
{
$this->_showOnlyThe($posts) ;
}
// else we have posts and comments
else{ $this->_showBoth($posts,$comments) ; }
}
private function _showBoth($posts,$comments){
// this is how you pass more then one data structure
// array, object, text, etc etc
// with $data['name']
$data['posts'] = $posts ;
$data['comments'] = $comments ;
// and call more then one view if necessary
$this->load->view('post/index', $data);
$this->load->view('comment/index', $data);
}
so this index method is only asking for data from models, and then depending on what if any data it gets back - it calls a separate private method and that method can call the appropriate views. In other words now you don't need to do this in your view
<?php if($posts): ?>
thats what you want to avoid, because then the view is making decisions about what to show. obviously some logic is going to happen in views, but as much as possible all decisions should happen in the controller.
Meta
First of all, I think you do actually want to DRY, as DRY means "Don't Repeat Yourself". I think you got the concept but reading that you "don't want to DRY" is kind of confusing ;)
Answer
Secondly: In a classical MVC approach (which CodeIgniter really much does), one does indeed let the controller handle the model that is then (or data from it) passed on to the view.
Now there are different concepts on how to retrieve all data that you want from the controller, e.g. really reading all of it out in a controller and then passing it on the the view, as compared to only passing the "post" models and let the view take out the posts comments in the view itself. I think both have valid reasons and you can decide which one to use (and there are others, too!), even though I prefer the latter one.
One alternative could be to use a "Decorator Pattern" (see Wikipedia), which seems to have an userland implementation in CodeIgniter only: https://github.com/ccschmitz/codeigniter-decorator
TL;DR
Your approach is imho fine, but you might look into decorator patterns (see above).
Related
I have function in ProductsController productsCount(). It give me amount of records in table.
public function productsCount() {
$productsAmount = $this->Products->find('all')->count();
$this->set(compact('productsAmount'));
$this->set('_serialize', ['productsAmount']);
}
I want to call this function in view of PageController. I want to simply show number of products in ctp file.
How can i do this?
You can use a view cell. These act as mini controllers that can be called into any view, regardless of controller.
Create src/View/Cell/productsCountCell.php and a template in src/Template/Cell/ProductsCount/display.ctp
In your src/View/Cell/productsCountCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class productsCountCell extends Cell
{
public function display()
{
$this->loadModel('Products');
$productsAmount = $this->Products->find('all')->count();
$this->set(compact('productsAmount'));
$this->set('_serialize', ['productsAmount']);
}
}
In src/Template/Cell/ProductsCount/display.ctp lay it out how you want:
<div class="notification-icon">
There are <?= $productsAmount ?> products.
</div>
Now you can call the cell into any view like so:
$cell = $this->cell('productsCount');
I think it would make more sense to just find the product count in the PageController. So add something like $productsAmount = $this->Page->Products->find('all')->count(); in the view action of PageController, and set $productsAmount. If Page and Products aren't related, then you can keep the find call as is as long as you include a use for Products.
Also check this out for model naming conventions: http://book.cakephp.org/3.0/en/intro/conventions.html#model-and-database-conventions
Model names should be singular, so change Products to Product.
you can not call controller method from view page. you can create helper, which you can call from view page.
here you will get a proper documentation to creating helpers-
http://book.cakephp.org/3.0/en/views/helpers.html#creating-helpers
It just depend on the kind of call you're making because there are 3 cases for your issue..
1- If you're calling by a link to click you simply do:
<?= $this->Html->link(_('Product number'),['controller' =>'ProductsController', 'action' => 'productsCount']) ?>
The 2 other cases are whether you want to render the result straight in that same view, then there are some workaround to do.
1- first you will need to check what are the associations between the Page table and the product table and use BelongTo or hasMany option to bind them togheter for proper use.
2- If no association between the tables then you will nedd TableRegistry::get('Produts'); to pass data from a model to another, just like this way in the Pages controller:
public function initialize()
{
parent::initialize();
$this->Products = TableRegistry::get('Produts');
}
But i quite believe that the first option is more likely what you described.
Also you can define static method as below
public static function productsCount() {
return = $this->Products->find('all')->count();
}
And use self::productsCount() in other action.
This is useful only if you need to get count multiple time in controller. otherwise you can use it directly in action as below:
$this->Products->find('all')->count();
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I have always coded controllers so that they simply call data from a model and then present that data to the view:
class ProjectViewModel
{
public $User = NULL; // Contains authentication levels etc
public $Projects = NULL;
}
class ProjectController
{
//...
public function ListProjects()
{
$viewModel = new ProjectViewModel();
$viewModel->User = $this->sessionRepository->GetSession();
$viewModel->Projects = $this->projectRepository->Projects();
return View::make( "ViewName", $viewModel );
}
}
Now in my view:
<ul>
<?php foreach( $Model->Projects as $project ) { ?>
<li>
<?=$project->Title?>
<?php
switch( $Model->User->Authentication->Type )
{
case AuthenticationType::ADMIN:
| <button>Edit</button>
break;
}
?>
</li>
<?php } ?>
</ul>
You see in my way of doing things, the person designing the view decides what is shown based off of the user's authentication... Don't take this just for how I have made it too, you can think about the model returning projects with a property that states whether they can or cannot edit a specific project... The main idea is there is 1 Boolean value that states if someone can or cannot do something to a project.
My colleague has gone for a different approach, which is interesting because he is defining within the controller, if a "button" (which may or may not exist if the view person decides to show it in a different way) should be shown or not:
class ProjectController
{
//...
public function ListProjects()
{
$viewModel = new ProjectViewModel();
$viewModel->User = $this->sessionRepository->GetSession();
$viewModel->Projects = $this->projectRepository->Projects();
$viewModel->Buttons = array(
"EditButton" => array(
"Name" => "Edit button",
"Show" => ( $viewModel->User == AuthenticationType::ADMIN ) ? TRUE : FALSE
),
"OpenProjectReportButton" => array(
"Name" => "Open project report",
"Show" => ( $viewModel->User == AuthenticationType::ADMIN ) ? TRUE : FALSE
)
);
return View::make( "ViewName", $viewModel );
}
}
In the view he uses buttons that have been pre-declared in the controller:
<ul>
<?php foreach( $Model->Projects as $project ) { ?>
<li>
<?=$project->Title?>
<?php if( $project[ "EditButton" ][ "Show" ] ) { ?>
<button><?$project[ "EditButton" ][ "Name" ]?></button>
<?php } ?>
</li>
<?php } ?>
</ul>
Although I understand why he might think this is a good idea, The controller is now taking on some of the work of the view... In fact he has gone as far as to say that the view will have buttons.. which the designer may disagree with...
It also means that if the view guy wants to add another button somewhere else, he's going to have to ask the controller guy to give him a new button in the array for something else... and just after doing that, he may then say, actually it's not a button, I just want to show an image instead...
Am I right in thinking this is wrong?
Overview:
A controller should fetch data from the model, and push it to the view for the view to display it how it wants to display it...
A view should use that data to decide how and what to show on the view... ( further more to this, if you are worried that an admin button is shown accidently, if they click on that button provided by the view engineer, it doesn't matter.. the user get's taken to another page which loads a controller which insists that actually, this user does not have access to this page... )
The view guy should be asking questions like, can this user edit projects... rather than have I been given a button that I can display to the view
What if the view guy decided actually, for design purposes, I want to show buttons that the user can't use... and provide a message stating why he can't use it... The controller in my colleagues example has provided a list of buttons that will now have to be ignored considering the view engineer (artist/designer) is decided actually, screw your controller... I want to show that button!
Am I right?
EDIT:: I've added new tags because i'm aware that PHP and C# people have very different approaches to problems... and i'm interested in the view from asp.net users aswell.
Proper MVC separation is simply only about separating responsibilities appropriately:
the model does all the work, everything your application "can do" is part of a thick model layer
the view visualises the state of the model, i.e. what's going on in your app, to the user (or to other entities)
the controller mostly just reacts to events (input) and directs them to appropriate actions to make something happen in the model and refresh the view if necessary; it's the plumbing between model, view and the rest of the world
As such, it's none of the controller's business to do anything which has to do with presentation. The view is also not simply a single .php HTML template. The view can be as thick as the model layer and its responsibility is to do anything that's necessary to produce useful output. The view should directly talk to the model to get the state information it needs as appropriate. Pretty much all of the code that's currently in your controller belongs into the view.
A couple of things, first expanding on what deceze said:
"Pretty much all of the code that's currently in your controller
belongs into the view"
I agree, your controller is junked up with stuff that shouldn't be there. Your controller should look like this:
class ProjectController
{
public function ListProjects()
{
return View::make("ViewName");
}
}
The data preparation works better inside Laravel's ViewComposers.
In Laravel they have a construct called the ViewComposer. It is designed to prepare data for your views.
You specify a class called:
class ViewNameComposer {
public function compose($view){
$view->model = new ProjectViewModel();
$view->model->User = $this->sessionRepository->GetSession();
$view->model->Projects = $this->projectRepository->Projects();
}
}
And then you register it with the view:
View::composer('ViewName', 'ViewNameComposer');
And when that view is rendered the composer hooks in and prepares the data for the view.
What is really awesome is that you can associate ViewComposer with partial view snippets.
This means that when you are using say the Blade templating engine and your page view has a boolean that determines whether or not to display some "other content", if you save that "other content" as a partial view snippet and conditionally include it
#if($someBool)
#include ('partials.ViewName.othercontent')
#endif
then you can only load the data that the partials.ViewName.othercontent needs (from the ViewComposer of partials.ViewName.othercontent) if it needs it.
What I like about this approach is it helps keep the views a bit cleaner and it gets rid of the junk that shouldn't be in the controller.
For more information on ViewComposers check out their documentation:
http://laravel.com/docs/responses#view-composers
I'm trying to understand MVC, and learning CI framework. I've some questions about MVC and some basic questions about CI.
1)Views are visual part of application as i read from tutorials, my question is: e.g There is a button "Login" but if user already logged in button will be "Logout". Where will that login check be? On controller or on view? i mean
//this is view//
<?php if($_SESSION('logged') == true):?>
Logout
<?php else: ?>
login
<?php endif; ?>
or
//this is controller//
if($_SESSION('logged') == true)
$buttonVal = 'logout';
else
$buttonVal = 'login';
//and we pass these value to view like
$this->view->load('header',$someData);
//this time view is like
<?=$somedata['buttonVal']?>
i just write theese codes as an example i know they wont work, they are imaginary codes, but i guess you got what i mean. Login check should be on controller or on view?
2)Should models contain only codes about data and return them to controller? For example there is a math, we get 2 value from database and multiply them and display them. Model will multiply or controller will do it?
here we load data with model and do math on controller:
//model
$db->query(....);
$vars=$db->fetchAll();
return $vars;
//controller
$multi = $vars[0] * $vars[1];
$this-load->view('bla.php',$mutli);
here we load data with model and do math on model too, controller just passes data from model to view:
//model
$db->query(....);
$vars=$db->fetchAll();
$multi = $vars[0] * $vars[1];
return $multi;
//controller
$multi = $this->model->multiply();
$this-load->view('bla.php',$mutli);
i mean with that, models should do only database works and pass data to controllers, controller do rest of work and send view to render? Or models do work, controllers get them and send them to view?
3)This is about codeigniter, i have a header which has to be in every page, but it has javascripts,css depending to page i'm using
<?php foreach ($styles as $style): ?>
<link id="stil" href="<?= base_url() ?>/css/<?= $style ?>.css" rel="stylesheet" type="text/css" />
<?php endforeach; ?>
this will be on every page, so in every controller i have
$data['styles'] = array('css1','css2');
$this->load->view('header', $headers);
i'm thinking to make a main controller, write this in it, and all my others controllers will extend this, i see something MY_Controller on CI wiki, is this MY_Controller same with what i'm doing? Are there any other ways to do this?
Sorry for bad English and dummy questions. Thanks for answers.
This is absolutely view logic, the correct way to do it in my opinion:
<?php if($logged_in):?>
Logout
<?php else: ?>
login
<?php endif; ?>
The value of $logged_in would probably be retrieved from a call to a library method:
<?php if ($this->auth->logged_in()): ?>
Authentication is one of those things you'll want access to globally, so you may be calling $this->auth->logged_in() in controller or views for different reasons (but probably not in models).
In every controller i have
$data['styles'] = array('css1','css2');
$this->load->view('header', $headers);
Yes you could extend the controller class with MY_Controller, but you're better off keeping this in the view/presentation layer. I usually create a master template:
<html>
<head><!-- load assets --></head>
<body id="my_page">
<header />
<?php $this->load->view($view); ?>
<footer />
</body>
</html>
And write a little wrapper class for loading templates:
class Template {
function load($view_file, $data) {
$CI = &get_instance();
$view = $CI->load->view($view_file, $data, TRUE);
$CI->load->view('master', array('view' => $view));
}
}
Usage in a controller:
$this->template->load('my_view', $some_data);
This saves you from loading header/footer repeatedly. In my opinion, presentation logic like which CSS file to load or what the page title should be belongs in the view whenever possible.
As far as models go, you want them to be reusable - so make them do what you need and keep it strictly related to data manipulation (usually just your database). Let your controller decide what to do with the data.
Not related to MVC, but in general you want to write as little code as possible. Redundancy is a sign that you could probably find a better solution. These are broad tips (as is your question) but hopefully it helps.
1) View logic should be simple and mostly if-then statements, if needed. In your example, either case would work but use the logic in the view. However, if you were checking for login and redirecting if not logged in, then that would occur in a controller (or a library).
2) Think of Codeigniter models as ways to access database functions - Create, Retrieve, Update, Delete. My (loose) rule of thumb is for Codeigniter models is to return results from update, delete or insert queries or a result set from a fetch query. Any applicable math can then occur in the controller. If this is a math operation that occurs EVERY time, consider adding it to a library function. See below...
3) Extending the controller is the proper and best way to accomplish this.
*) Not to add more to your plate, but also be sure to learn about Codeigniter Libraries. For example, in your controller you could load your library. You then call your library function from your controller. The library function calls a model which retrieves your database result. The library function then performs math on that function and returns the result to the controller. The controller is left with little code but a lot is accomplished due to the library and model.
The user lo-gin check should be in the controller.
This should be the first function that need to be invoked in the constructor.
Below i have given the sample code which redirects the user to the login page if he is not logged in, hope this would give you some idea,
<?php
class Summary extends Controller {
function Summary() {
parent::Controller();
$this->is_logged_in();
}
function is_logged_in() {
$logged_in = $this->session->userdata('logged_in');
if (!isset($logged_in) || $logged_in != true) {
$url = base_url() . 'index.php';
redirect($url);
exit();
}
}
?>
The button change can be implemented in the view by checking the session variable in view and making decisions accordingly.
Please take look at this link
I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);
Quick question about general MVC design principle in PHP, using CodeIgniter or Kohana (I'm actually using Kohana).
I'm new to MVC and don't want to get this wrong... so I'm wondering if i have tables:
categories, pages, notes
I create a separate controller and view for each one...? So people can go to
/category/#
/page/#
/note/#
But then lets say I want to also be able to display multiple notes per page, it would be bad to call the note view in a loop from the page view. So should I create some kind of a function that draws the notes and pass variables to that function from the note view and from a loop in the page view? Would this be the best way to go about it, if not how else should I do it...?
Thanks,
Serhiy
Yes, instead of just passing 1 entity (category, page, note) to your view, pass a list of entities. With a loop inside the view, you can display the whole list.
That view may call another one (or a function) that know how to display one entry.
I would personally have a "show" method for one item and a "list" method for multiple. In your controller you can say something like $page_data['note'] = get_note(cat_id,page_id) for the "show" method and $page_data['notes'] = get_all_notes(cat_id) for the "list" method.
Then in your view, you loop over the $page_data['notes'] and display HTML for each one. If the list view is using the same "note" HTML as the "show" view, create a template or function to spit out the HTML given a note:
// In your "list" view
foreach($n in $page_data['notes']){
print_note_html($n)
}
//In your "show" view
print_note_html($n)
The print_note_html function can be a helper method accessible by all views for Notes. Make sense?
You can loop in the View. The View is allowed can also access the model in MVC. See: http://www.phpwact.org/pattern/model_view_controller
You don't need to have a controller (or model) for each table.
In CodeIgniter I create a separate helper file where I put functions that return the markup for UI elements that may need to be included multiple times in the one view.
In your example, I would create a function to return the markup for a note.
application/helpers/view_helper.php
function note($note)
{
return '<div class="note">' .
'<h2>' . $note->title . '</h2>' .
'<p>' . $note->contents . '</p></div>';
}
I would normally auto-load this helper file. And then in the view I would do something like this.
echo note($note);
For a list of notes in a view, I would iterate the list calling this function.
<div class="note-list">
<?php foreach ($notes as $note) : ?>
<?php echo note($note); ?>
<?php endforeach; ?>
</div>
I found that including a view many times in another view was slow. Thats why I did it this way.
Edit
I just dug into the CodeIgniter Loader class and sure enough a PHP include is being done every time you call
$this->load->view('view_name');
This means that if you use this method to display a list of 20 notes, you're going to be doing 20 separate includes.