how to show a view within a view - MVC

I want to create a page that will act as a sidebar on all pages. This sidebar shows some widgets which should get records from the database. I don't want to have to copy and paste this sidebar code into every single other view but rather want to require or include it.

I am not sure how to go about this but what I tried was:

Created a folder in the views folder called "widgets" and put an index.php file in it.
Created a Widgets.php file in the controllers folder
Created a Widget.php file in the models folder
Send the data from the model to the controller and sent to the view.
If I go directly to the index.php file in the widgets view, it works fine and the database records are displayed as I want.

However, if I try and include this index file in another view somewhere else, I get an "undefined index" and "invalid argument supplied foreach()".

So, what is the proper way to achieve this?

PS.I am not using a framework and this is my controller:

public function index()
    {

        $widgets = $this->widgetModel->getWidgets();

        $data = [

        'widgets' => $widgets

        ];

        $this->view('widgets/index', $data);
    }

Open in new window

LVL 1
Black SulfurAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Steve BinkCommented:
OK, so if you go to the widgets view independently, it works.  If you try to render it as a "sub-process" within another view, it does not.  

There are a number of things that can go wrong, depending on how your MVC components are initializing themselves.  A common strategy is to pull parts of their config from the environment, e.g., parts of the query string, or even the path of the request.  If your components are using this strategy, it is likely that, when visiting your "standard" view, the widgets view is being initialized with incorrect parameters.

From your code, it looks like you are instantiating the widget model and view directly.  I would first recommend trying to do it the standard way, via the controller.  If necessary, implement a feature in the controller to pass in arbitrary initialization configuration points.  Meaning, give your controllers a way to receive configuration instead of initializing from the environment.  Then, in your standard view, call the controller with your contrived configuration.  It should be able to render normally, which you can then assign as an available variable in your view/template.
0
Black SulfurAuthor Commented:
Thanks Steve,

I have a bit of a blank stare on my face after reading your post. I am going to post some more code which will hopefully show what I am doing and then you could pinpoint where the problem is within the code.

In my views folder, I have a posts folder with an index (shows all posts) and article.php (show individual articles clicked on)

The article view looks like:

<div class="row">
	<div class="col-md-8 col-sm-9">
		<div class="article-body">
			<h1><strong><?php echo sanitize($data['posts']->title); ?></strong></h1>
			<h5>
				<?php echo date('d F Y - h:ia', strtotime($data['posts']->article_date)); ?>
			</h5>
			<p>
				<?php echo nl2br($data['posts']->article); ?>
			</p>
		</div>
	</div>
	<?php include APPROOT . '/views/widgets/index.php'; ?>
</div>

Open in new window


Here is my POSTS controller:

class Posts extends Controller {
	
	public function __construct() {
		
		$this->postModel = $this->model('Post');
		
	}
	
	public function index() {
		
		$posts = $this->postModel->getPosts();
		
		$data = [
			
			'posts' => $posts
		];
		
		$this->view('posts/index', $data);
	}
	
	public function article($id, $slug) {
		
		$posts = $this->postModel->getPostBySlug($id, $slug);
		
		$data = [
			
			'posts' => $posts
		];
		
		$this->view('posts/article', $data);
	}
	
}

Open in new window


Here is the view for the widget/sidebar in views folder / widgets folder / index.php

<div class="col-sm-3 col-md-offset-1">
	<div class="blog-sidebar">
		<div class="sidebar-widget">
			<h5><a href="<?php echo URLROOT; ?>/posts" class="a2">Latest News</a></h5>
			
				<?php foreach ($data['widgets'] as $widget) { ?>
			<ul>
				test
			</ul>
			<?php } ?>
			
		</div>
		<div class="sidebar-widget">
			<h5><a href="events" class="a2">Events</a></h5>
			<ul>
				<?php //sidebar_events($link); ?>
			</ul>
		</div>
	</div>
</div>

Open in new window


And my widgets controller:

class Widgets extends Controller {
	
	public function __construct()
	{
		$this->widgetModel = $this->model('Widget');
	}
	
	public function index()
	{
		
		$widgets = $this->widgetModel->getWidgets();
		
		$data = [
			
		'widgets' => $widgets
			 
		];
		
		$this->view('widgets/index', $data);
	}
}

Open in new window


I have included the widgets view in the article view which is giving me the errors as mentioned in my original post.
0
Steve BinkCommented:
You're using static initialization, which means your controllers initialize in the same way every single time, with no consideration for environmental differences.  So think about what is going on...

When you visit the widgets page, you are calling your widgets controller, which initializes your widgets model and pushes it into your widgets view.  When you visit your posts page, you are calling your posts controller, which initializes your posts model and pushes it into your posts view.  Your posts view, near the end, is calling the widgets view directly.  What model does the widgets view have access to, if you have not pushed anything into it?

Instead, make your posts controller call the widgets controller.  The widgets controller should run through its process, and return the rendered content.  The posts controller should take that content and add it to the $data array you are pushing into the posts view.

The reason I emphasize returning content is that it appears you are currently rendering content immediately, which is not going to work for what you want.  That means when you call the widgets controller, widgets are going to render immediately, without any opportunity for you to decide where that content should appear structurally.  Since you must necessarily call it prior to your posts view, the widget will necessarily appear (structurally) before the posts view content.  Judging from the template example you posted, you want it just the opposite.

There are two options to do what you want:

1) Use ob_* functions around your new calls to the widget controller, so you can capture the rendering.  This is clumsy, and will likely cause you headaches down the road if you implement it as a special case.

2) Implement similar ob_* style functionality system-wide.  You would do this from further upstream, i.e., whatever is calling your posts controller originally should have facilities to handle the view returning content instead of rendering it.
0
C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.

Steve BinkCommented:
Take a look here: Basic MVC flowchart
Notice how the flow centers on the controller?  The controller talks to the client, the model, and the view.  Your structure right now has the view talking to the client, by way of immediate rendering of content.  That means, at most, you can render one view.  You need your views to return rendered content to the caller, which allows your controller (or another view acting in lieu of a controller) to call additional views.
0
Black SulfurAuthor Commented:
haha, I think I am just too dumb to understand this. I thought of initially trying to take the db data from the widget model and put it into the posts controller as well. like you said, call the widgets controller in the posts controller. But the problem with that is then on every page that has the sidebar I would have to do that and repeat myself over and over.

For example, on the article page (posts controller) I have to call the widgets controller. Then on the contact page which uses the contact controller, I would again have to put the widgets controller into the contact controller, and keep doing this wherever I wanted a sidebar. I had just wanted to create a sidebar which had widgets that could easily be included in any view I want simply by putting in a line of code in the view to display that sidebar.
0
Black SulfurAuthor Commented:
So, I put the widgets model into my posts controller and put that into the $data array. That seems to have done the trick:

public function article($id, $slug) {
		
		$posts = $this->postModel->getPostBySlug($id, $slug);
		$widgets = $this->widgetModel->getWidgets();
		
		$data = [
			
			'posts' => $posts,
			'widgets' => $widgets
		];
		
		$this->view('posts/article', $data);
	}

Open in new window


It works, but honestly, I don't like it. I wouldn't want to have to do this in every controller for every view that I want to display the sidebar.
0
Steve BinkCommented:
The problem you're having with this setup is that your target view (e.g., article) must also create the view for the widgets.  Yes, you were able to access the widget data through the model, but you're not using the widget view.  

You already have an MVC structure for widgets - the widgets controller, model, and view.  You need to leverage them instead of reproducing code in each place.  Imagine if your widget view, instead of rendering the code, returns it as a string:
public function widget($id) {
  $model = $this->model->getWidgets();
  $rendered_content = $this->view('widgets', $model);
  return $rendered_content;
}

Open in new window


Then in your posts controller, you would only need to do this:
public function article($id, $slug) {
  $posts = $this->postModel->getPostBySlug($id, $slug);
  $widgets = WidgetController::execute();
  $data = [ 'posts' => $posts, 'widgets' => $widgets ];
  $this->view('posts/article', $data);
}

Open in new window


>>>  I wouldn't want to have to do this in every controller for every view

That statement is more important than you probably realize.  One of the primary goals of object-oriented programming is to not repeat code.  If you find yourself doing the same thing over and over, then it is likely that process belongs in a class.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Black SulfurAuthor Commented:
I am new to OOP and MVC for that matter, and I am going to probably be horribly wrong when I say this, but I have been thinking about it and could the problem be that I have not instantiated "widget" which is why I get that error when trying to include it? Perhaps I need to instantiate it first on the page I am trying to use it?
0
Steve BinkCommented:
Yes, that is part of the problem I described.  You're not actually leveraging the MVC features of the widget because you're not calling it from the controller.  While that is possible to do, you'll have to do all the controller's pre-work work.  It would be easier to call it from the controller.

BUT, if you call it from the controller, you're likely calling it with your default mode, which renders it instead of returning the renderable HTML.  That leads to my suggestion of making your controllers a little more responsive to varying environments, and being able to set those environments as you instantiate/invoke it.
0
Black SulfurAuthor Commented:
Instead of this:

include APPROOT . '/views/widgets/index.php'; 

Open in new window


I tried:

$widgets = new Widgets();
echo $widgets->index();

Open in new window


But that gives me an error:

require_once(libraries/Widgets.php): failed to open stream: No such file or directory in <b>/Applications/MAMP/htdocs/mysite/app/bootstrap.php

in bootstrap.php I have

spl_autoload_register(function($className){
	require_once 'libraries/' . $className . '.php';
});

Open in new window

0
Steve BinkCommented:
Does "libraries/Widgets.php" exist?  

1) Note the capitalization.  Your file system could be case-sensitive.
2) Note the relative directory.  The will be relative to the current script's directory.  You can check it with "__DIR__".
0
Black SulfurAuthor Commented:
Hmm. No, it doesn't. Libraries folder contains, Controller.php, Core.php and Database.php

Widgets.php is found in my controllers folder.
0
Black SulfurAuthor Commented:
I solved this by changing the code in my bootstrapping file:

spl_autoload_register(function($className) {
	$folders = [
		'libraries/',
		'controllers/',
	];
	foreach ($folders as $f) {
		$path = __DIR__ . '/' . $f . $className . '.php';
		if (is_file($path)) {
			include_once $path;
		}
	}
});

Open in new window

0
Steve BinkCommented:
Excellent.  I'm happy to hear you found a resolution.  Good luck moving forward!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.