Unset the variables of a view for sub-views in codeigniter - php

Controller
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Main extends CI_Controller
{
public function index()
{
$this->load->model('Event');
$todays_events = $this->Event->get_todays_events();
$data = array(
'todays_events' => $todays_events
);
$this->load->view('main/index', $data);
}
}
?>
main/index view
<?php $this->load->view('partial/header'); ?>
<?php $this->load->view('components/calendar/mon_to_wed'); ?>
<?php $this->load->view('partial/footer'); ?>
components/calendar/mon_to_wed
(Has access to $todays_events, why is this?)
<div id="calendar_today">
<h1>Whats Happening</h1>
<?php foreach($todays_events as $event) : ?>
<?php var_dump($event); ?>
<?php endforeach; ?>
</div>

I've read the CodeIgniter core files, while hacking the core may solve this issue, another method exists that help to prevent defining variables in inner-views.
In the first view file, do this instruction:
foreach ($_ci_vars as $key => $value) $_ci_vars[$key]=NULL;
$this->load->view('your-inner-view', $_ci_vars);
$this->load->view('your-second-inner-view', $_ci_vars);
$this->load->view('your-third-inner-view', $_ci_vars);
// and so on...
I'll update my post, if I find a better solution
UPDATE:
Finally! I found the real solution, it is better to make your own Loader class instead of using default one. do the instructions below:
Copy Loader.php class from /system/core/ to your /application/core/
Find $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); at line #805
change/replace that line to $this->_ci_cached_vars = $_ci_vars;
CodeIgniter has a variable cashing, once you use load->view() method, the variables will be cached in an array, and the second using of load->view() caused to merge cached variables and new variables if exists and then cache the result as a new array (which contains the old variables).
So, stop using of array_merge() would be the solution ;)

The given answer might work for single views, when assembling a view out of multiple sub-views, any data for the overall view gets deleted by the above fix.
What works for me is to explicitly set those data items for the sub-view to null if they are not needed.
For instance the sub-view expects $a, $b and $c.
The first time we render the sub-view we pass $data['a'=>'whatever', 'b'=>'whatever', 'c'=>'whatever'] and the view is rendered correctly.
The second time we pass $data['a'=>'whatever'], $b and $c get rendered in the sub-view with the data from the first call.
If instead we pass $data['a'=>'whatever', 'b'=>null, 'c'=>null], b and c do not get rendered in the sub-view.
This is of course assuming you are checking whether the data is null before you use it in the sub-view.

Related

Injecting PHP Variables into HTML Code

This has probably been asked before, but I couldn't find the answer to my question with some preliminary searches, so here it is:
A very simple example of my current method of injecting PHP variables into HTML is as follows:
HTML (file.php):
<tag><?php echo $variable; ?></tag>
PHP:
$variable = "value";
ob_start();
include "file.php";
$results = ob_get_clean();
This achieves the correct result just fine, but it annoys me every time that I have to copy and paste those three lines to get variables injected into my HTML files. There's a chance I might want to change the details of this injection functionality at a later date, and it's currently scattered in a couple hundred locations throughout my code.
In a perfect world, I would move those three lines to a separate function that takes the file path as an input, and returns the "compiled" result. However, the function would then no longer have access to the calling scope, and I would have to pass the variables in as another input. The only thing I'm able to think of for doing that is:
function injectVariables($filePath, array $variables)
{
//Built-in PHP function, extracts array key => value pairs into variable $key = value pairs in the current scope
extract($variables);
ob_start();
include $filePath;
return ob_get_clean();
}
That certainly gets the job done, and it will be what I implement if there aren't any better solutions, but it seems less than ideal to me. It involves creating an array every time this runs, just to loop through it and extract all the variables, which just feels a bit wrong to me.
Does anyone know of any other methods that might work?
Not really sure what you are asking, but here is my answer
I don't know the structure of your code, but I hope you are using a MVC approach (or at least something which deals with classes) so that you can do the following:
In your main controller, you create a class variable like viewData which will be an array. And you put in it everything you want
$this->viewData['username'] = $username;
$this->viewData['myArray'] = array('foo' => 'bar');
$this->viewData['menuSubview'] = 'path_to_menu_subview.php';
Then, you create a render function.
public function render()
{
extract($this->viewData);
ob_start();
include("myfile.php");
return ob_get_clean();
}
And in myfile.php (with the HTML) you can simply do what you used so far
<div id="menu"><?php include($menuSubview);?></div>
<p><?=$username;?></p>
<p><?=$myArray['foo'];?></p>
The whole code can be something like this.
class Something {
protected $viewData;
protected $viewFile;
public function logic()
{
$this->userProfile();
echo $this->render();
}
public function userProfile()
{
$this->viewData['username'] = 'John The Ripper';
$this->viewFile = 'myFile.php';
}
public function render()
{
extract($this->viewData);
ob_start();
include($this->viewFile);
return ob_get_clean();
}
}
Here's a simplified class that stores data and allows for recursive rendering of files that all have access to the save data form the initial instance.
class View {
// view data
protected $_data = [];
/**
* Set view data
*/
public function __set( $key, $value )
{
$this->_data[ $key ] = $value;
}
/**
* Get view data
*/
public function __get( $key )
{
if( array_key_exists( $key, $this->_data ) )
{
return $this->_data[ $key ];
}
return null;
}
/**
* Render view file
*/
public function render( $file )
{
if( is_file( $file ) )
{
$view = $this;
ob_start();
include( $file );
return ob_get_clean();
}
return 'File not found: '.$file;
}
}
Just use the variable $view inside your included files to access the data in the class, or render() another partial file that can do the same thing, and so on.
// Bootstrap a View instance and add some data
$view = new View;
$view->dataOne = 'valueOne';
$view->dataTwo = 'valueTwo';
// Render main template
echo $view->render( 'template.php' );
Inside template.php
<header>
<?= $view->render( 'header.php' ) ?>
</header>
<h1><?= $view->dataOne ?></h1>
<p><?= $view->dataTwo ?></p>
Nothing is wrong with your injectVariables() function, it is in fact a good idea.
You shouldn't have performance impact anyway (If this is your main concern your are doing premature optimization !)
Your view (HTML) shouldn't know about the internal of your application. (You split responsability - this is a huge subject that I won't talk deep)
You know if something end-up into $variables it has been build/formatted or is revelent to be display for $filePath.
In complex system you may end up with a variable with a pdf generator, why would the HTML want that? The only purpose of the HTML is to DISPLAY HTML data.
Some variables from your logic will end-up in $variables almost every time such as session informations (Who is currently logged), for your case this is normal.
In the perfect world if you put a class into $variables it should be designed only for purpose of your HTML even if it is almost the same object as your logic.
As an exemple I designed a Session class in my logic. It does have method such as Logout() and EnsureIsAlive() as well as field such as FullName. FullName is going to be use by my HTML because I want to display it in the header of my page.
I should have a Session class for the HTML that only contains a FullName field because the HTML has only one job and is it to display data. Login() and EnsureIsAlive() are specific for the logic and aren't related at all with displaying data.
In reallity time is always a constraint and because you are writing all by yurself from scratch you may end-up just sending the logic Session class into $variables.
Design note : I'm a C# programmer and we always use class over array to pass parameters as a good pratice and it does affect the exemple about how to pass FullName to your HTML. In your case, instead of creating a class dedicated for the HTML you could also create a simple array :
$variables['Session'] = array('FullName' => $mySession->fullName).
That way you still avoid your HTML to have access to the unrelated method specific for your logic. I'm not sure if it is a good pratice in php...
Keep context variables clean
If you do call injectVariables() more than once (for different $PathFile) you may not want the same variables.
One injectionVariables() could be for a widget, another for a complete complete page.
Why would a TimeWidget need your totalInvoice variable?
You also have the ability to prepare multiples $variables at the same time because each injectVariables() will have his own parameters.
I'm not up-to-date but I know phpBB template system use (back near 2000) the exact same pattern as your injectVariables()).
You wrote that each of your page do call injectVariables(), you could also (but it may be complex) do it only in one file - like php frameworks.
Framework handle HTTP requests into one specific file
(eg. http://yoursite.com/hello/world or http://yoursite.com/login would call (internaly) http://yoursite.com/index.php?page=hello&subpage=world and http://yoursite.com?page=login).
Then this page (index.php in my exemple) would include a specific file (controller) according with his GET parameters ($_GET['page'] and $_GET['subpage']).
The controller job would be to fetch data, apply logic then fill $variables.
Then index.php will call injectVariables() with the releated HTML.

Passing database results into header file in PHP-MVC

I'm starting a new project using the http://www.php-mvc.net framework but have never had to include database results in the header file before and not sure how to go about it. I need to pull a list of current categories and there ID's from the database and use them to populate a menu.
The header.php file is in /views/_templates. The normal way of passing database results to the view is to run the query in the relevant model, get the data in the controller, pass the data from the controller to the view, then loop over the data with a foreach loop in the view. The problem being the _template files don't have any kind of controller for them.
The best I can come up with is to use include and include a view file from the home controller, using that controller to get the results and pass them to a menu.php in the views/home folder.
/views/_templates/header.php
<li class="dropdown">
<?php include 'views/home/menu.php'; ?>
</li>
/views/home/menu.php
foreach ($links as $link){
<li><?php echo $link->name; ?></li>
}
the above code has been shortened its more for principle than a working example.
The method I have come up with works but I wanted to know if there's a more elegant way of doing things?
As I commented:
No need to store in session you could use a Twig global so its available in all templates and you could make sure that your base controller runs a preExceute to pull all the data together and then add that global.
That might look something like this:
abstract class MyBaseController extends Controller
{
private $view = null;
private prepareView()
{
$twig_loader = new Twig_Loader_Filesystem(PATH_VIEWS);
return new Twig_Environment($twig_loader);
}
public function getView()
{
if ($this->view === null) {
$this->view = $this->prepareView();
}
return $this->view;
}
protected function preRender()
{
// whatever logic you need to prepare the menu data as $links
$this->getView()->addGlobal('links', $links);
}
public function render($view, $data_array = array())
{
$this->preRender();
// render a view while passing the to-be-rendered data
echo $this->getView()->render($view . PATH_VIEW_FILE_TYPE, $data_array);
}
}
Now depending on what data you need to build your stuff for $links you may or may not need to get a bit more elaborate. Especially given how the URL params are handled in the Application class. I really hope you're only doing this as a learning experience because this "framework" you've found isn't really good for much other than learning how you might implement MVC.

Two controllers for one view, CodeIgniter?

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');

How to run an action of controller file from .ctp file?

I want to call an action along with its .ctp file of a controller file from another .ctp file.
for e.g.
users_controller.php has an action called list_category() and I want to call it from /app/views/pages/index.ctp file. Not just call list_category but also want to show its html output(I mean also list_category.ctp should be rendered).
Thanks.
Create an element, for instance list_category.ctp.
In the element use requestAction to get the data:
<?php
$categories = $this->requestAction('/users/list_categories');
?>
<?php foreach($categories as $category): ?>
<?php // Your display code goes here ?>
<?php endforeach; ?>
In your controller make sure you return the data you want.
<?php
function list_categories() {
return $this->User->Category->find('all');
}
?>
You can reuse the code for your list_category.ctp view.
There is an overhead when using requestAction but it is often less than people believe.
Can you do that with routing? I'm not sure of the syntax off the top of my head but I think you can specify that method that the controller runs when you land on that page
It seems wrong, what is it that you're trying to accomplish? How about elements?
How about calling the controller from your main controller, then pass its results to your layout. Finally use an element to render the output there and also use the element to render the output on that other controller too. That way you don't have duplicate layouts. Just one element used by two controllers.
This is very similar to the way Rails creates its layouts when you 'bake' them. It creates an equivalent of a element to use in the add and edit layouts.
This can be done with requestAction, but be aware, that it's expensive and you should be careful with it.

CodeIgniter: Create new helper?

I need to loop lot of arrays in different ways and display it in a page. The arrays are generated by a module class. I know that its better not to include functions on 'views' and I want to know where to insert the functions file.
I know I can 'extend' the helpers, but I don't want to extend a helper. I want to kind of create a helper with my loop functions.. Lets call it loops_helper.php
A CodeIgniter helper is a PHP file with multiple functions. It is not a class
Create a file and put the following code into it.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('test_method'))
{
function test_method($var = '')
{
return $var;
}
}
Save this to application/helpers/ . We shall call it "new_helper.php"
The first line exists to make sure the file cannot be included and ran from outside the CodeIgniter scope. Everything after this is self explanatory.
Using the Helper
This can be in your controller, model or view (not preferable)
$this->load->helper('new_helper');
echo test_method('Hello World');
If you use this helper in a lot of locations you can have it load automatically by adding it to the autoload configuration file i.e. <your-web-app>\application\config\autoload.php.
$autoload['helper'] = array('new_helper');
-Mathew
Some code that allows you to use CI instance inside the helper:
function yourHelperFunction(){
$ci=& get_instance();
$ci->load->database();
$sql = "select * from table";
$query = $ci->db->query($sql);
$row = $query->result();
}
Well for me only works adding the text "_helper" after in the php file like:
And to load automatically the helper in the folder aplication -> file autoload.php add in the array helper's the name without "_helper" like:
$autoload['helper'] = array('comunes');
And with that I can use all the helper's functions
To create a new helper you can follow the instructions from The Pixel Developer, but my advice is not to create a helper just for the logic required by a particular part of a particular application. Instead, use that logic in the controller to set the arrays to their final intended values. Once you got that, you pass them to the view using the Template Parser Class and (hopefully) you can keep the view clean from anything that looks like PHP using simple variables or variable tag pairs instead of echos and foreachs. i.e:
{blog_entries}
<h5>{title}</h5>
<p>{body}</p>
{/blog_entries}
instead of
<?php foreach ($blog_entries as $blog_entry): ?>
<h5><?php echo $blog_entry['title']; ?></h5>
<p><?php echo $blog_entry['body']; ?></p>
<?php endforeach; ?>
Another benefit from this approach is that you don't have to worry about adding the CI instance as you would if you use custom helpers to do all the work.
Create a file with the name of your helper in /application/helpers and add it to the autoload config file/load it manually.
E.g. place a file called user_helper.php in /application/helpers with this content:
<?php
function pre($var)
{
echo '<pre>';
if(is_array($var)) {
print_r($var);
} else {
var_dump($var);
}
echo '</pre>';
}
?>
Now you can either load the helper via $this->load->helper(‘user’); or add it to application/config/autoload.php config.
Just define a helper in application helper directory
then call from your controller just function name like
helper name = new_helper.php
function test_method($data){
return $data
}
in controller
load the helper
$this->load->new_helper();
$result = test_method('Hello world!');
if($result){
echo $result
}
output will be
Hello World!
To retrieve an item from your config file, use the following function:
$this->config->item('item name');
Where item name is the $config array index you want to retrieve. For example, to fetch your language choice you'll do this:
$lang = $this->config->item('language');
The function returns FALSE (boolean) if the item you are trying to fetch does not exist.
If you are using the second parameter of the $this->config->load function in order to assign your config items to a specific index you can retrieve it by specifying the index name in the second parameter of the $this->config->item() function. Example:
// Loads a config file named blog_settings.php and assigns it to an index named "blog_settings"
$this->config->load('blog_settings', TRUE);
// Retrieve a config item named site_name contained within the blog_settings array
$site_name = $this->config->item('site_name', 'blog_settings');
// An alternate way to specify the same item:
$blog_config = $this->config->item('blog_settings');
$site_name = $blog_config['site_name'];

Categories