I have two functions loadView() and render() in my view class.
public function loadView($view){
if(file_exists(APP.'view/'.$view)){
require(APP.'view/'.$view);
}
else{
die(APP.'view/'.$view.' not found.');
}
}
public function render($view,$data = array()){
if(!empty($data)){
extract($data);
ob_start();
//$this->loadView($view); -------------- not woriking
require(APP.'view/'.$view); ------ working
$this->output = ob_get_clean();
echo $this->output;
}
}
Whenever I call the loadview function from the render its not working. But if I include the view file directly after extracting data, it works. Anybody can tell me Why is this happening here or any solution ?
This will not work, because require() includes the required code in the place of the require() statement and in the scope of that location. In this case, the scope is the loadView() method. Within that method scope the extracted variables from the render() method are not available.
Instead of finding a solution for that, I would encourage you to use a template engine for your views like Twig. This is standard practice in MVC frameworks. Then, you could do something like this:
public function render($view,$data = array()){
$template = $twig->loadTemplate($view);
echo $template->render($data);
}
Related
I would like to create some global functions that make use of an existing class method. I don't have all of the supporting functionality for the getConfig() method shown, but it's irrelevant to my question:
class Init {
public function getConfig($type) {
if (isset($this->config[$type])) {
return $this->config[$type];
}
$file = dirname(__FILE__, 3).'/app/config/'.$type.'.php';
if (!file_exists($file)) {
return false;
}
$contents = include($file);
$this->config[$type] = $contents;
return $this->config[$type];
} // End getConfig
}
// class Init gets instantiated in my bootstrap file
Now, in my helpers.php file:
function app_config($key) {
$init = new Init(); // Is there any better way of doing this?
return $init->getConfig('app')[$key];
}
So, the helper function app_config may be used in 20 places throughout the app for any given request. Not to mention other helper functions that utilize the Init class as well as other classes.
What is the best way to setup these helper functions? Should they be instantiating these classes over and over again? (This makes it extremely difficult with classes which have specific parameters passed to the constructor)
I am building a website using php and i have used many functions through different pages.
I was thinking of collecting the functions to a single functions.php file and include it on the page i want to use the function but that would also include other functions which are useless on that page, so is there a way to include only the nessesary functions from the functions.php file instead of including them all?
Thanks,
You may not have the Luxury to pick and choose the functions to load from a File. However, you may try putting all those Functions of yours into a Trait. Then use them within a Trivial Class wherein it would be imported. Here's an Example:
<?php
trait PreferredFunctions {
public function doActionOne($param1){
// CODE! CODE!! CODE!!!
return null;
}
public function doActionTwo($param2){
// CODE! CODE!! CODE!!!
return null;
}
public function doActionThree($param3){
// CODE! CODE!! CODE!!!
return null;
}
}
class Trivial{
use PreferredFunctions;
public function __construct() {
}
}
$trivial = new Trivial();
$result1 = $trivial->doActionOne(1);
$result2 = $trivial->doActionTwo(2);
$result3 = $trivial->doActionThree(3);
I have a view class with a function to build the view
class view {
//...
public function build() {
$view = $this;
$data = $this->resources->data;
function s($value) {
return \classes\tools\html\html::specialChars($value);
}
require $this->viewFile;
}
//...
}
And a view some view files
<?php var_dump($view);
// works fine, variables passed ok ?>
<?= s($data->someUnsafeString) ?>
<?php //Fatal error: Call to undefined function s() ?>
I could define the function s In each view file but I really dont want to have to do that.
I could pass the function as a variable $s=function(){..} but I'd prefer not too
I could call the static function in the view directly but even that is more long winded than I'd like:
<?= html::s($data->someUnsafeString) ?>
Is this possible? or any other suggestions?
If your project has a Front Controller (one file - entry point),
then you can to declare your function in common namespace in this or required(#require) by this file.
Common namespace is code without namespace or
namespace {
function s() {
}
}
You may be misunderstanding what you're doing there. Functions aren't scoped in PHP. Your function s(..) inside view::build is merely declaring a regular global function. Doing that inside functions isn't anything special. You can simply declare functions conditionally in PHP, e.g.:
if (!function_exists('s')) {
function s(..) ..
}
So just put that function declaration into a separate file and include it as needed; no need to define it again and again separately in each file.
My current implementation:
class SomeController extends AppController
{
function someaction()
{
$d['text'] = "ahoy!";
$this->render("someactionView", $d);
}
}
And in AppController:
function render($file, $data = "")
{
require "views/" . $file . ".php";
}
And the $data will be available in the views file. Is this a correct implementation? Are there any fallacies with this implementation?
And the $data will be available in the views file. Is this a correct
implementation? Are there any fallacies with this implementation?
Basically you do implement it like the most frameworks do. There's a couple of problems with that:
A controller takes an input and sends an output (which breaks the Single-Responsibility Principle)
A view is tightly coupled to HTML. Because of this, you cannot re-use the same view for another stuff, like XML, JSON.
If you do require "views/" . $file . ".php"; in render() method - you again tighly couple it. What if you change the location of views? Then you would have to slightly rewrite your method. This approach merely kills reuse-ability.
To refresh your basic knowledge:
Controller (also known as Editor)
Serves only singular purpose. It changes model state - that is, it should take an input that comes from $_POST, $_GET, $_FILES, $_COOKIE. In controller only variable assignment should be done and nothing more.
class Controller
{
public function indexAction()
{
$this->view->setVar('age', $this->request->getPostParam('age'));
$this->view->setVar('user', $this->request->getPostParam('user'));
//...
}
}
View
A view has a direct access to a model. In order to make make views more re-usable and maintainable you'd better pass required things as function parameters (or via setters)
class View
{
public function render($templateFile, array $vars = array())
{
ob_start();
extract($vars);
require($templateFile);
return ob_get_clean();
}
}
How the view should be initialized and how the variables should be passed to it?
First of all - a view should be instantiated outside MVC-triad. Since a controller writes either to view or model - you'd pass variables to view via controller.
$model = new Model();
$view = new View($model);
$controller = new Controller($view);
// This will assign variables to view
$controller->indexAction();
echo $view->render();
Note : In real world scenario, a model isn't a class, but abstraction layer. I call it Model for demonstration purposes.
IMO the render() method belongs to the view and not to the controller. The code should look like this:
Controller:
class SomeController extends AppController
{
function someaction()
{
$d['text'] = "ahoy!";
$view = new SomeActionView();
$view->assign('data', $d);
echo $view->render();
}
}
View Base Class:
class View
{
protected $data;
function render($template) {
ob_start();
// you can access $this->data in template
require "views/" . $template . ".php";
$str = ob_get_contents();
ob_end_clean();
return $str;
}
function assign($key, $val) {
$this->data[$key] = $val;
}
}
Extend View class
class SomeActionView extends View
{
public function render($template = 'someActionTemplate') {
return parent::render($template);
}
}
Is this a correct implementation? Are there any fallacies with this implementation?
Short answer: no and several.
First of all, what you have there is no a view. It's just a dumb php template. Views in MVC are instance, that contain the UI logic for the application. They pull information from model layer and, based on information they receive, create a response. This response can be simple text, JSON document, a HTML page assembled from multiple templates or simply a HTTP header.
As for controller, it's only task is to alter the state of model layer and (on rare occasions) the current view. Controllers do not initialize the views nor do the populate templates.
I am working on creating my own very simple MVC and I am brainstorming ways to go from the controller to the view. Which involves sending variables from a class to just a plain old PHP page.
I am sure that this has been covered before, but I wanted to see what kind of ideas people could come up with.
//this file would be /controller/my_controller.php
class My_Controller{
function someFunction(){
$var = 'Hello World';
//how do we get var to our view file in the document root?
//cool_view.php
}
}
Some kind of hashtable is a good way to do that. Return your variables as association array which will fill all the gaps in your view.
Store your variables as a property in your controller object, then extract them when rendering
class My_Controller {
protected $locals = array();
function index() {
$this->locals['var'] = 'Hello World';
}
protected function render() {
ob_start();
extract($this->locals);
include 'YOUR_VIEW_FILE.php';
return ob_get_clean();
}
}
You can define those magic __get and __set methods to make it prettier
$this->var = 'test';
I'm also developing my own simple MVC and the most simple way to do it is ...
class My_Controller
{
function someFunction() {
$view_vars['name'] = 'John';
$view = new View('template_filename.php', $view_vars);
}
}
View class
class View
{
public function __construct($template, $vars) {
include($template);
}
}
template_filename.php
Hello, <?php echo $vars['name'];?>
I highly recommend you to take a look at PHP Savant http://phpsavant.com/docs/
I'd checkout Zend_View and how it accomplished view rendering.
You can get the source of View and AbstractView on github - unfortunaly I don't find the current repository (in svn) that easy to browse.
Essentially the view variables are contained in a View object (which your controller would have access to), then the template (plain old php document) is rendered inside that object. That method allows the template access to $this.
It would be something like:
<?php
class View
{
public function render()
{
ob_start();
include($this->_viewTemplate); //the included file can now access $this
return ob_get_clean();
}
}
?>
So in your controller:
<?php
class Controller
{
public function someAction()
{
$this->view->something = 'somevalue';
}
}
?>
And your template:
<p><?php echo $this->something;?></p>
In my opinion this pattern allows you much flexibility with the view.
I created my own MVC for the free PHP course I'm conducting for a handful of people wanting to get better at PHP.
By far the best way to do this is to use the Command + Factory pattern.
E.g.
interface ControllerCommand
{
public function execute($action);
}
In each controller:
class UserController implements ControllerCommand
{
public function execute($action)
{
if ($action == 'login')
{
$data['view_file'] = 'views/home.tpl.php';
}
else if ($action == 'edit_profile')
{
$data['view_file'] = 'views/profile.tpl.php';
$data['registration_status'] = $this->editProfile();
}
return $data;
}
}
From your main front controller:
$data = ControllerCommandFactory::execute($action);
if (!is_null($data)) { extract($data); }
/* We know the view_file is safe, since we explicitly set it above. */
require $view_file;
The point is that every Controllercommand class has an execute function and that returns its view and any data for that view.
For the complete MVC, you can access the open source app by emailing me at theodore[at]phpexperts.pro.