Hoping this is some kind of silly oversight on my part, but I've had little luck finding information about this or other examples of similar usages of Laravel. I'm developing a Laravel 4 site whose content is populated not with a local database, but with posts from a specific Tumblr blog, via the Tumblr API.
Every Tumblr post has a certain type associated with it ("text", "video", "photo", etc.), and each type has entirely different types of content that need to be spit out, so I have a Blade template for each post type, inheriting from a master post Blade template. (Everything is just a stub right now.)
To fill out the front page, in my controller I'm populating an array with those post views ($postViews). What is maddening is that if I loop through $postViews and echo out each individual view in the controller, it contains the proper content--all three views in the array show up on the final site inside their correct templates.
But when I send $postViews off to my welcome view, and then loop through $postViews inside THERE, it renders three instances of ONLY the first view of the array. I have no idea why.
Here's the relevant code. As you can see in the welcome template, I tried looping through $postViews in the welcome view both with native PHP and with the Laravel template syntax. They both exhibit the same behavior: showing only the first of the three posts, three times.
// controllers/HomeController.php
class HomeController extends BaseController {
public function showIndex()
{
$client = new Tumblr\API\Client(CONSUMERKEY, CONSUMERSECRET);
$tumblrData = (array) ($client->getBlogPosts(BLOGNAME));
$postViews = array();
foreach ($tumblrData['posts'] as $post) {
$post = (array) $post;
$type = TumblrParse::getPostType($post);
$postViews[] = View::make('tumblr.'.$type, array('post' => $post));
}
foreach ($postViews as $p){
echo $p;
// This works! It displays each post view properly before
// before rendering the welcome view, but I need them to
// be inside the welcome view in a specific place.
}
return View::make('home.welcome')->with('postViews', $postViews);
}
// views/home/welcome.blade.php
#extends('layouts.master')
#section('title')
#parent :: Welcome
#stop
#section('content')
<h1>Hello World!</h1>
<?php
foreach($postViews as $p) {
echo $p; // Just outputs the first of the array three times
}
?>
#foreach($postViews as $p)
{{ $p }} // Just outputs the first of the array three times
#endforeach
#stop
// views/layouts/post.blade.php
<div class="post">
#yield('postcontent')
</div>
// views/tumblr/photo.blade.php
// Other post types have their own views: video.blade.php, text.blade.php, etc.
#extends('layouts.post')
#section('postcontent')
<h1>This is a photo post!</h1>
<?php var_dump($post); ?>
#stop
I really appreciate any help! I'm new to Laravel, which I'm sure is obvious. And for all I know, I'm doing something wrong in PHP generally rather than in Laravel specifically.
In this instance it probably makes sense to render each view as a string before appending it to the $postViews array using the view render method.
$postViews[] = View::make('tumblr.'.$type, array('post' => $post))->render();
What about nesting views. Check documentation.
Related
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).
Within my views I am trying to reuse a partials, so I can create a list of latest posts anywhere in the site / sidebar by using the blade #include where the passed variable is the number of posts to be included, like this:
#include('widgets.lastestposts', array('numPosts' => '10')
However the problem I have is how to get the Post data for the correct number of posts within the partial?
I could pass through a list of all posts via Post::all() using the controller or even a View::composer and then within the partial use a #for|#endfor loop to only show the correct number based on the 'numPosts' value.
However this doesn't feel right and I am sure there must be a better way than pulling a complete list of Posts when I may only need 5 or 10.
I tried View::composers but I could find how to pass through a variable so I can get the correct number of Posts returned. I can't access the parameter 'numPosts' via
$view->getdata()
as I expect 'numPosts' needs to be passed to the view via the controller, rather than the Blade file - either that or I messed up!
Am I missing something easy here or is what I am looking to do actually a very bad idea and I should be doing something else?
Any pointers are most gratefully received. Thanks!
(ps - I was looking to be able to do this via the blade file rather than setting up the number of posts in the controller to allow our designers/HTML coders to simply add the widgets and parameters to the views rather than have to mess with controllers.)
I would do this using View Composers. You can pass data to the composer with your include:
#include('widgets.lastestposts', array('numPosts' => '10')
and then from within the view composer you should be able to access that param like so:
View::composer('widgets.latestposts', function($view)
{
$view_data= $view->getData();
$post_count = $view_data['numPosts'];
//You will have to implement something to do this
$post_data = Post::getLatestPosts($post_count);
and then you can pass the post data back with:
$view->with('posts', $post_data);
}
and then from within your blade partial widgets.latestposts you can iterate over $posts to display the posts.
I know you said in your post that you tried this method, but I am fairly certain that this approach should work. Double check all your filenames, file extensions (.blade.php) etc...
Hope this works.
Using a view composer is pretty easy:
You can, for instance, store your number of posts in a Session var:
View::composer('*', function($view)
{
$view->with('numPosts', Session::get('numPosts'));
}
Or just hardcode them:
View::composer('*', function($view)
{
$view->with('numPosts', 10);
}
And use it in your view:
<?php $i=1; ?>
#foreach($posts as $post)
{{ $post->title }}
<?php
$i++;
if ($i > $numPosts) break;
?>
#endforeach
Assuming that you've passed $posts to your view:
$posts = Post::all();
return View::make('your-view')->with('posts', $posts);
But, remember, you also can do things like
$posts = Post::orderBy('created_at', 'desc')->take(Session::get('numPosts'))->get();
// or
$posts = Post::orderBy('created_at', 'desc')->take(10)->get();
// and
return View::make('your-view')->with('posts', $posts);
So you won't have to filter them again in your view.
I'm looking for a clean way to pass variables to partial views. Consider the following example code:
In my controller I do:
$this->view->articles = $arrayWithArticles;
$this->render('articles.phtml');
In my articles.phtml view I do:
foreach($this->articles as $article) {
// show article
$this->render('comments.phtml');
}
In another controller I do:
$this->view->products = $arrayWithProducts;
$this->render('products.phtml');
In my products.phtml view I do:
foreach($this->products as $product) {
// show product
$this->render('comments.phtml');
}
As you can see I use the same (partial) view comments.phtml to display comments about written articles as well as products. The 'comments' I want to display are in $article->comments and $product->reviews. The partial view will need these to display them.
What would be a clean way to pass them to the partial view. I really don't want to do:
$this->comments = $article->comments;
$this->render('comments.phtml');
Because this would potentially become a pain to keep track off (i.e. setting the same view variables in both the controller as in the view).
Is there a clean solution to pass variables to partial views?
Well, I think adding a parameter to your render() method would be sufficient. Maybe something like...
$this->renderSubView($fileName, $data);
Then in renderSubView() you could do whatever it is that you need to do with the array and return the rendered partial view. This way you don't need to redeclare the variable in view, just pass the data appropriate for that specific partial when it is being rendered.
Hey guys, I am new to CodeIgniter and need some help. I have a controller that formats the content area of a post. The problem is that I also need to create a sidebar that contains dynamic groups, and a right column that contains recent posts. This isn't hard, the problem I'm running into is that I want the sidebar, and right column on every page, and I don't want to recode the same bits to get the data in every controller.
What would be the best way to do this without copy/paste?
There are a lot of ways to do this.
1) Templating: This is my preference for most cases (because my templates are complex), I render my view into a variable using something like:
$content = $this->load->view('myview', $page_data, true);
Then I load it into the template parser (fyi you could load it into another view too) like this:
$this->load->library('parser');
$data = array(
'page_title' => 'My Page Title',
'page_content' => $content,
'side_bar' => side_bar(), // function which generates your side bar
'right_col' => right_col() // function which generates your right column
);
$this->parser->parse('my_template', $data);
Then your template is like:
<html>
<head>
<title>{page_title}</title>
</head>
<body>
<div>{side_bar}</div>
<div>{page_content}</div>
<div>{right_col}</div>
</body>
</html>
2) Load another view in your view: (assumes you menu is a view not a controller) Something like this:
<?php $this->load->view('menu_view'); ?>
3) PHP Includes: exactly how you would do it in plain PHP (just include a url which points to a controller which returns a menu), Something like this:
<?php include("/common/sidebar"); ?>
Codeigniter will render that page and then include it.
4) AJAX.. i use this if the content in the "template" content is less important, like banners, suggested related item lists and such.
Use PHP to generate a static HTML page, such as side_bar.html...
Then you can include it on other pages.
You could look into HMVC. It's especially suited for "widget"-type areas like you are talking about.
Essentially what you will do is create two full MVC structures - one for your sidebar and right column, including a controller, a model(if required), and a partial view. Then, you can call this controller directly from the main view to pull the required content in to the page.
To actually call it from within a view, just place the following in the markup wherever you want the sidebar to appear:
<?php echo modules::run('module/sidebar/index'); ?>
The index isn't required, but I put it there to demonstrate that you can call different methods using modules::run(). You can also pass an unlimited number of parameters to modules::run().
In code igniter, there is an optional third parameter to $this->load->view that lets you return a rendered view as a string, which can in turn be used for assignment. What you can do is create a master template, that has all the common parts, as a very simplified example:
<html>
<head>
</head>
<body>
<?php echo $sidebar; ?>
<?php echo $content; ?>
<?php echo $right_column; ?>
</body>
</html>
Then you can create a private function in your controller to populate the dynamic content of your common parts, and combine them with your content and master template:
private function BuildTemplate($view, $data) {
// Generate sidebar content
$sidebar_data['...'] = 'blah blah';
$master_data['sidebar'] = $this->load->view('sidebar', $sidebar_data, true);
// Generate right column data
$right_data['...'] = 'blah blah';
$master_data['right_column'] = $this->load->view('right_column', $right_data, true);
// Now load your content
$master_data['content'] = $this->load->view($view, $data, true);
// Merge it into the master template and return it
return $this->load->view('master' $master_data, true);
}
Then in your appropriate controller method:
public function index() {
$data['...'] = 'blah';
echo $this->BuildTemplate('index', $data);
}
Which will pull everything together for you. You can optionally add extra arguments to BuildTemplate if you want to add things like page specific titles or scripts.
I'm not sure if your problem is in the view, or in the (dynamic) data to be shown in the (common parts of) that view.
If it's the later (as seems to suggest the phrase 'I don't want to recode the same bits to get the data in every controller'), then you have several options. For example.
Put the logic to get the 'common' data in some function outside the controller, as a helper or inside some model, and call it from your controllers.
Make your controllers inherit your own custom controller, that implements that data gathering function.
Refactor your two controllers into a single controller, with different functions for each scenario.
1-Create a custom library class in library folder with the below code
if (!defined('BASEPATH')) exit('No direct script access allowed');
class LoadView{
function __construct(){
$this->CI =& get_instance();
}
function load_view($page){
$this->CI->load->view('header');
$this->CI->load->view('sidebar');
$this->CI->load->view($page);
$this->CI->load->view('footer');
}
}
2-Now load this library in your controller like this
$this->load->library('loadview');
3-Now call the library method and simply insert your page name and you don't have to include header,sidebar and footer again and again as they will be dynamically included by your library.
$this->loadview->load_view('about.php');
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.