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.
Related
It's really convenient how MVC patterns allow you to define view a and then load variables into it via the controller. For the sake of argument let us take CodeIgniter as an example:
Controller:
Class Example extends CI_controller(){
function show_page(){
$data = array('msg'=>'Hello World');
echo $this->load->view('hello',$data);
}
}
View (hello.php):
<h1><?php echo $msg; ?></h1>
I have taken over an old project written years ago where there are redundant html code everywhere. It has no pattern whatsoever just straight up poorly structured code.
I wanted to create a class that has a function that will fetch all HTML code from a file in one folder, feed variables to them and show the result. Like so:
Folder structure:
View_folder
- hello.php
Class
- view_class.php`
Main:
<?php
$data['msg'] = 'Hello World!';
echo $view_class->get_view('hello.php',$data);
?>
Is it possible to achieve this? Can someone give an example function on how to do this. Thanks.
Sure thing, that's what the frameworks are doing. Here's a really basic overview of how I'd expect that to work:
function view($template, $data){
extract($data); // this pulls all of the first-level array keys out as their own separate variables
ob_start(); // this turns on **output buffering** which is the method we'll use to "capture" the contents of the view (there are surely other ways)
require $template; // you should prepend some sort of fixed path to this where all of your templates will reside
echo ob_get_flush(); // turns off output buffering, returning the buffered content as a string which we then send to the browser.
}
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.
I am somewhat confused on the proper way to perform the following. Is this a class a function or an object? (Not sure) So here goes (please be kind) I'm learning codeigniter/php simultaneously.
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'<a href="' .base_url().
'main/logout">Log Out</a>) </b>';
I wrote this code, which checks if a session is set and if true echo the details. I currently have this code on my view/main However I want to have this code displayed on all pages.
What is the proper way to do this?
Do i create a view/header which is auto loaded on each page and insert this code there?
Do I create a class/public function in a helper file and load it where needed?
Do I create a class/public function in a library file and load it where needed?
Do I create a public function in a controller?
-EXTRA-
My assumption is option 1 but I'm curious what if I want to display this information in another location on a particular page? I don't want to have to copy/paste the code block because that is sloppy.
How would I set it up so that I can just call it where i need it?
e.g. $this->load->helper('check_for_sess');
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class isLive
{
public function is_live() {
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
}
On the view I tried this:
<?php
$isLive = new isLive;
$isLive->is_live();
?>
This however doesn't work and causes a ' function userdata() on a non-object error '
Can someone please explain the proper way to achieve my desired solution and the correct syntax. Thanks in advance!
-UPDATE-- Tried to create a library - still getting error.
#Created a library
class CheckSess
{
function IsLive()
{
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
{
$check_msg = 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
return $check_msg;
}
}
#In the View
<?php echo $this->CheckSess->IsLive(); ?>
#in the controller
$this->load->library('CheckSess');
--UPDATE--
class CheckSess {
function IsLive()
{
$CI =& get_instance();
$is_live = $CI->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
{
return 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
}
}
Set this^ is the libraries folder
the view shows <?php echo $this->CheckSess->IsLive(); ?>
and the controller shows $this->load->library('CheckSess');
please advise. Thanks again!
ERROR---
( ! ) SCREAM: Error suppression ignored for
( ! ) Fatal error: Call to a member function IsLive() on a non-object in C:\wampserver\www\test-app\application\views\homepage.php on line 56
Call Stack
Time Memory Function Location
1 0.0005 151432 {main}( ) ..\index.php:0
2 0.0014 187792 require_once( 'C:\wampserver\www\test-app\system\core\CodeIgniter.php' ) ..\index.php:202
3 0.0277 1537000 call_user_func_array ( ) ..\CodeIgniter.php:359
4 0.0277 1537048 Main->index( ) ..\CodeIgniter.php:359
5 0.0283 1540200 CI_Loader->view( ) ..\main.php:8
6 0.0283 1540640 CI_Loader->_ci_load( ) ..\Loader.php:419
7 0.0287 1566584 include( 'C:\wampserver\www\test-app\application\views\homepage.php' ) ..\Loader.php:833
The best ways to write such reusable in CodeIgniter are Models and Libraries.
With libraries being better because the code and logic you use isn't actually what models in MVC are, but with CodeIgniter and especially for a beginner it's pretty much ok to write this in models if you are not planning to distribute or share this code in any way.
I would suggest reading more on what goes where in CodeIgniter, you can find plenty information here, on SO or on official CI forums, but as a quick reference, I will write a list of what goes where.
Views
Static, dumb data output, formatting, iteration(for displaying tables, lists, etc.).
The only things you want to use here are some of the basic PHP functions, CodeIgniter helpers, for, foreach, while loops and conditionals (if, case)
Controllers
Form data capture and validation, catching data from models, sending data to views, redirects.
Models
Querying, reading, writing and updating data from databases, remote sources (APIs), files.
Helpers
Often used functions that are expected to be used in views - formatting HTML, parsing and displaying trees as lists <ul><li></li></ul>, shorthand functions for other functions like
function mydate() {
return date("d.m.Y");
}
Stuff that does not use any of the CodeIgniter libraries/models (Technically it can use, but should be avoided in most cases).
Libraries
Custom classes and objects, data processing, API interfaces, complex of the aforementioned
If you want to display some view file with data, I would put it in a library. You can put it in to a model. In my projects when I use some parts, I have a library/model (difference in our case is minimal), lets call it pmodel.
class Pmodel extends CI_Model() {
function ShowHeader() {
$data = array(
'user' => $this->usermodel->GetCurrentUser()
}
$this->load->view('header', $data);
}
function ShowMyPart() {
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'<a href="' .base_url().
'main/logout">Log Out</a>) </b>';
}
}
Now anywhere in the view files you can call $this->pmodel->ShowMyPart() if you have loaded it before (models of this type should be autoloaded anyway).
There is one little change I would do as it's considered as good practice - never echo anything from anywhere but the view files, return values instead.
Change the echo keyword to return and in your view file simply write echo $this->pmodel->ShowMyPart().
Why should you do that? Because, if you would like to call any method that echoes something from the controller right between loading header and footer(eg.), CodeIgniter will echo this before the header as CodeIgniter does not echo your loaded views right away, it saves them to output storage and sends the finalized output right before destructing himself. More reading can be found here.
But you can do even better - use a view file to output any data you want, just create a helper and load it from your model/library as you do in your controllers.
Hope that helps!
EDIT
For your edited question, the reason why you are getting errors with your library is that your library doesn't know that an instance of CodeIgniter even exists as you are not extending your library from it, as you do with your models and controllers. You don't use $this to refer to CodeIgniter classes in your libraries, as $this is a reference to your library itself, which is a separate class. To use CodeIgniter classes you should get a CodeIgniter instance:
$CI =& get_instance();
And then you can use $CI instead of $this to call CodeIgniter classes by changing the line:
$is_live = $this->session->userdata('email');
to
$is_live = $CI->session->userdata('email');
You could make a basic html helper and autoload it in the config file.
Then you can make a function for this...
function displayLoginStatus($em)
{
$html = '';
if($em)
{
$html = 'Hello, '.ucwords($em).' <b>(Not you? '.'Log Out) </b>';
}
return $html;
}
then you call it like a normal function:
<?php echo displayLoginStatus($this->session->userdata('email')); ?>
but ideally you would place something like this in a template or header file that wraps your standard page views. There's no need to make a function out of something like this since typically you don't have the login / logout in multiple places on the page.
It could be condensed simply to:
<?php if($this->session->userdata('email')): ?>
Hello, <?php echo ucwords($this->session->userdata('email')); ?> <b>(Not you? Log Out) </b>
<?php endif; ?>
I prefer to make a site template and wrap the views with it. Makes common elements much easier to handle.
Some people template it this way:
http://codeigniter.com/forums/viewthread/88335/
There are many ways you could handle this, but the general idea is to separate your most common elements into one template view. I don't know of a succinct way of instructing you on how to do this so I will only update this post if you feel the need for much more explanation on templating.
helper docs:
http://codeigniter.com/user_guide/general/helpers.html
For multi-language usage of CMS, they translate terms by a function similar to
function __($word) {
include 'fr.php';
if(!empty($lang[$word])) {$translated=$lang[$word];
} else {
$translated = $word;
}
return $translated;
}
Since we need to use this function several times in a php page, as all words and phrases will be echoed by __(' '); does the function need to include the language time every time, or it will be cached for the function after first load?
Since the language file contains a complete list of words and phrased used throughout the website (thousands of key/value), pho needs to load this long array into memory every time a page is visited. Is it really the best approach to add multi-language feature to a CMS?
If you can't use gettext() for some reason, you'd be better off, with something like this, to put it into an object with the included language strings as a static array, something like:
class Message {
private static $_messages = array();
public static function setMessageLibrary($sMessageLibrary) {
require_once $sMessageLibrary;
self::$_messages = $aMsgs;
}
public static function getMessage($sMessageId) {
return isset(self::$_messages[$sMessageId]) ? self::$_messages[$sMessageId] : "";
}
}
Your message library file (included with the setMessageLibrary() static function), of which you'll have one per language, will need a variable in it called $aMsgs which might look something like:
// Messages for fr-FR
$aMsgs = array(
'hello_everybody' => "Bonjour tout le monde"
...
and so on
);
Since it's all static but within the object you can effectively cache that included language file by setting it at the start of your script.
<?php
Message::setMessageLibrary('/lang/fr-FR/messages.inc.php');
echo Message::getMessage('hello_world');
echo Message::getMessage('another_message');
echo Message::getMessage('yet_another_message');
?>
All three messages will then reference the single language array stored in Message::$_messages
There's no sanitisation, nor sanity checks in there, but that's the basic principle anyway ... if you can't use gettext() ;)
1) it won't be cached, use include_once instead
2) no, i think gettext is doing it another/better way
IIRC, it will do some caching.
No, it's not. Check out gettext.
I'm using this code in a views field template (in this case views-view-field--all-members--uid.tpl.php):
<?php
$users_friends = flag_friend_get_friends($user->uid);
$users_friends_ids = array();
foreach ($users_friends as $id => $value) {
$users_friends_ids[] = $id;
}
?>
It basically gets the user ids of friends and puts them in an array so I can check if the field matches any of the user ids.
So my problem is that I don't want to have this within this template (for a few reasons), but if I don't I can't access the array. How can I make this array globally accessible?
Without knowing your "few reasons", I can't say if this is the answer for sure. My own reasons would probably be that I don't want the same code executing a bunch of times, and I'd rather not have the same exact code in multiple places.
I would then create a function with a static variable to hold the friends array.
function mymodule_get_friends_ids() {
// pull in the current global user variable
global $user;
// call up the static variable
static $users_friends_ids;
// return if this static var has already been set
if (is_array($users_friends_ids)) {
return $users_friends_ids;
}
// if we hit here, then this function has not been
// run yet for this page load.
// init array
$users_friends_ids = array();
// if user is anon, no need to go on
if (user_is_anonymous()) {
return $users_friends_ids;
}
// get friends array
$users_friends = flag_friend_get_friends($user->uid);
// build ids array
foreach ($users_friends as $id => $value) {
$users_friends_ids[] = $id;
}
return $users_friends_ids;
}
Now in your templates, you can call mymodule_get_friends_ids() in as many places as you want, and the working code below the first return will only get executed the first time it is called.
Coder1's advice is very good - it keeps you from populating your global variable namespace with a lot of junk. It's probably the most "elegant." It might not be the easiest to use if you are rather new to PHP (which I'm guessing might be the case if it's hard to get your head around returning arrays, but that's ok).
However, if this is really a priority, you probably don't care about having one extra global variable.
I suppose I may be stating the obvious here - but you can, at pretty much any point in execution (provided the information you need has already been generated - e.g., the $user variable has been populated), do this:
$GLOBALS['users_friends_ids'] = /* your code goes here */
Then in your template, you access this by ...
$friendsArray = $GLOBALS['users_friends_ids'];
Or you can simply use the construct
global $user_friends_ids;
when you want to initialize the variable, or access it inside a function or class (which is the case for your template files - they are called inside functions, so you need to globalize or use the $GLOBALS array, which is "automagically" all of the variables active in the global namespace).
The most "logical" place to do this would be inside a module using one of the many hooks available, to execute this code only once. hook_init() might do it for you, if the user object is already loaded at this point (not sure, you'll have to test). But you might not want to figure out making Drupal modules (it's not that difficult).
If you are doing this inside a template (and though it's not good practice, many Drupal site owners with a beginning knowledge of PHP put everything in templates), you'll want to know which template code is being executed when. Node template code tends to be executed before page template code - which is logical, since otherwise the variables for node content in the page template wouldn't be populated.
If you have listings of nodes, they'll be calling this code multiple times, so you'll end up doing something similar to what Coder1 is describing. If you don't want to create your own small module, you could put the function declaration he's written in your theme's template.php file, since it's called only once. You don't want to put function declarations in the tpl.php files, since they are sometimes called more than once (and you aren't allowed to declare functions more than once).
If you have a hard time understanding the function and the return, you can always do something like this in your code (which is very, very inelegant - but it's better to have inelegant code that you do understand, than elegant code that's you don't).
if(!isset($GLOBALS['users_friends_ids'])) {
$GLOBALS['users_friends_ids'] = /* your code here */
}