I have an MVC system which serves my web content through a URI. (www.something.com/view/me) and controllers serve a model and then a view.
Sometimes, I have content which requires client side interaction before it can generate content. In this case, a search based on location which needs the client to provide the location. Therefore I am calling the page, then calling a different URI using ajax and retrieving the html content to place on the page.
This works when I place the php/html directly in a model and echo it. I then use javascript in the ajax call to fill the void with the new content. But this is not neat. I would rather have all my presentation (even the dynamically generated stuff) in my views. How can I change the view class in my MVC to allow this?
I should point out that my current view class uses 'require' to output the view. However this i not suitable for ajax returns. Only a direct output will work.
I have tried file_get_contents('/view/generated_view') with a file location but this outputs raw code with no php parser in the flow. (I have also tried to run it as a direct url file_get_contents('www.something.com/view/generated_view') but the rules of MVC don't allow direct access to views.) I have also tried eval() but again, this has not worked and I want to avoid.
I will carry on and use the model to generate the html for output but is there something I am missing? a method I have not thought of?
You could use something like this.
class View {
protected $template;
protected $viewName;
protected $params;
public function __construct($viewName, $params = array()){
$this->viewName = $viewName;
$this->params = $params;
$this->render();
}
protected function render(){
if(empty($this->viewName)){
throw new Exception("ERROR: the view name is empty.");
}
$this->template = $this->getContentTemplate();
echo $this->template;
}
protected function getContentTemplate()
{
$templatePath = TEMPLATE_PATH . "/{$this->viewName}.php";
if(!is_file($templatePath)){
throw new Exception("ERROR: The template {$this->viewName} do not exists.");
}
$view = (object) $this->params; // the object $view can be used directly in the template
ob_start(); // opens output buffer
require($templatePath); // require template file, it's hold on the buffer
$template = ob_get_contents(); // get the buffer content
ob_end_clean();
return $template;
}
}
It´s a simple view class but you'll get it.
Related
I have a PHP class where I have to invoke PHP header function to show a webpage. As pointed out from this post, header should be followed by exit callback. Since this situation is very common in my classes, I've defined a method in parent class:
class ParentClass
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
I would like to invoke this method from children classes:
class ChildClass extends ParentClass
{
public function someFunc()
{
$this->header($some_url);
}
}
PHP documentation says that exit terminates the current script. My question is: does the exit function terminate child script even if it is contained in parent class?
EDIT
In my specific situation, I am using a MVC design pattern and ChildClass is a controller. Inside it, sometimes I need to show a view, sometimes I need to redirect to another url. Let me explain it with a practical example.
Suppose to have a website with a login section. When login page is displayed to users (login data not submitted), login controller should show login view. This view contains a form with an action like action="login.html". When data is submitted, login controller is invoked and checks login data: if login is successful, user is redirected to his admin section.
class UsersController extends BaseController
{
public function login()
{
try
{
if(isset($_POST['submit']))
{
// check login data, throw exception in case of error
// if success, redirect to admin section
$this->header('admin.html');
}
else
{
// show login view
}
}
catch(Exception $e)
{
// show login view (with error)
}
}
}
class BaseController
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
Since this situation is quite common in my controllers, I've preferred to define a header method in BaseController instead of typing everytime
header('Location:someURL.html');
exit;
In my OP, I only wanted to be sure that $this->header('admin.html'); callback would terminate current login method, even if it is defined in BaseController script.
Hope it's a little clearer now.
As already descripted in the comment, exit will terminate everything, i.e. the webpage immediately stops executing, including clean up functions and finally blocks.
So, you should consider using exit very carefully because many things might happen: data doesn't get written to the database when you're not using auto-commit (unless you commit the data before calling exit). Auto-commit is not enabled by default in PHP's MySQL module (as far as I know).
Here is an example:
class B extends A {
public function someFunc() {
# you might wanna use partent instead as
# pointed out by Ding in the comments, but
# maybe someFunc does more that just doing
# the redirect.
$this->header("http://google.com");
}
}
try {
print("Ok...");
$obj = new B();
$obj->someFunc();
print("Nahh..."); # doesn't get called/
} finally {
print("Cleaning up."); # doesn't get called, either.
}
Instead of calling the exit method, you should rather implement a clear MVC design pattern. Here is a very quick example:
<?php
class Response {
# use private here and use appropriate
# getters and setters.
public $status_code = 200;
public $content = "";
public $headers = array();
}
class HomeView extends View {
# called when a get request is made.
public function get() {
$response = new Response();
$response->content = "Hello world."
}
}
class RedirectionView {
public function get() {
$response = new Response();
$response->status_code = 301; # use 302 if moved only temporarily.
$response->headers["Location"] = "http://google.com";
}
}
function processRequest() {
# load appropriate view programatically
$view = new RedirectionView();
$response = $view->get();
http_response_code($response->status_code);
foreach ($response->headers as $headerName => $headerValue) {
header(sprintf("%s: %s", $headerName, $headerValue));
}
print($view->content)
}
?>
Note that this is not really a MVC design pattern (the model is missing and, well, it's not clear what the controller, however, that's what django (a Pyhton framework) uses, too). You might wanna check out PHP MVC frameworks (a quick google search will do the trick) or implement your own (my example might be a good start).
Edit 1:
Using exit is probably ok (but I wouldn't use it because I believe it is bad practice). If you're able to edit the design approach you're using, I'd make your View BaseClass get/post method (or whatever you're the method that returns response object. Note: if you're using prints to show the response to the user, try to add a Response which contains all the data you're request needs to show. This is better than having prints (or echos) everywhere in your code).
Using the response object, your code would then be able to either just set the location header (if it's a redirect) or show the message. So you don't need to "kill" any part of the webpage and it will terminal when the execution ends. But again: you're probably good to go with an exit call in your method (unless you're encountering any issues with your code (database transactions that aren't committed, statistic data that is not updated (because its after the exit statement). Exit will simply terminate your script completely.
When sending out e-mails in Zend Framework 2, I want to use view script templates such that I can leverage a layout for my various e-mail templates. The idea is that I have a layout which has the shared markup and echoes out content somewhere. Exactly how layouts work in general. To accomplish this, I am trying to add a child view model to my layout view model and render it with the PhpRenderer.
public function someMethod() {
$child = new ViewModel();
$child->setTemplate('test-template');
$layout = new ViewModel();
$layout->setTemplate('email-layout');
$layout->addChild($child, 'content');
$phpRenderer = new \Zend\View\Renderer\PhpRenderer();
$phpRenderer->setCanRenderTrees(true);
$resolver = new \Zend\View\Resolver\TemplateMapResolver();
$resolver->setMap($this->serviceLocator->get('ViewTemplateMapResolver'));
$phpRenderer->setResolver($resolver);
$output = $phpRenderer->render($layout);
}
Layout view script:
Hello from the e-mail layout script!
<?php echo $this->content; ?>
What happens is that only the layout's content is stored in $output and $this->content within the layout view script is NULL.
What I am trying to do is to store the HTML in a string such that I can send it in an e-mail. I instantiate a child view model, which represents the view script that I would like to render within the layout. Then I instantiate the layout view model and add the child view model. Then I create a PhpRenderer and add a TemplateMapResolver to it such that it will resolve templates by using the template map. Lastly, I call the render method on the PhpRenderer which takes a ViewModel as its first parameter. I have added both templates to my template_map configuration and the template map is set correctly with the correct names and correct paths.
The thing is that I do not see any handling of child views in its render method. This makes me think if I am using the PhpRenderer class incorrectly and perhaps that is why it doesn't work. I have checked the documentation for nesting view models, and I am doing the same thing, except not returning the view model from a controller action. If I return $layout from a controller action, it works.
If I look at Zend\View\View's render method, I can see that it renders child views. It would seem as if I cannot use the PhpRenderer like I want to. But can I use the Zend\View\View for this? The render method does not return anything, but triggers some events. I am not too familiar with the event manager yet, so I am not sure how/if I can capture the output if using this method. Would it be sensible to listen to the response event? I would have to check which view was rendered I guess (or detach my listener after doing my work). I don't know if that's any good.
Any ideas are more than welcome. Thank you!
$this->content is null because PhpRenderer just render self level not including children view models.
Actually the ZF2 MVC rendering are using Zend\View\View->render() but not Zend\View\Renderer\PhpRenderer->render(), so you need to render tree templates exactly like what Zend\View\View does:
foreach ($layout as $child) {
if ($child->terminate()) {
continue;
}
$child->setOption('has_parent', true);
$result = $view->render($child);
$child->setOption('has_parent', null);
$capture = $child->captureTo();
if (!empty($capture)) {
if ($child->isAppend()) {
$oldResult=$model->{$capture};
$layout->setVariable($capture, $oldResult . $result);
} else {
$layout->setVariable($capture, $result);
}
}
}
$output = $view->render($layout);
See ZF2 source code to know more:
https://github.com/zendframework/zf2/blob/master/library/Zend/View/View.php#L222
I was too sleepy when I asked the question, so sorry for that, anyway to make things clear I prepared the question for 2 hours.
I'm trying to organize my code and decided to organize it mvc'ish(mvc-like), I don't know if I can follow all the principles, but I wanted to be at least close to that.
My application has a front-controller (dunno if my definition is right), so that all the http-request of my application will be passing through a single point, in my case the index.php in the root directory of my application.
Having said that I have set it up like that, you can imagine that I used .htaccess to direct all request to index.php.
I exploded the url and created an array out of it, $url[] like so. So whenever I access my app like this http://localhost/app/pagename it'll be accessing a controller (pagename_controller)
I did it like this :
$file = $controller_path . $page . '_controller.php';
if (file_exists($file)) {
require $file;
$class_name = ucfirst($page) . '_controller';
$target = new $class_name();
}
also I wrap it up in a Container, the 'decorator pattern', for future use, validations maybe.
like this :
$controller = new Wrap($target);
$controller->index();
I don't know if the use of $controller variable name is appropriate so please forgive me when it is all wrong.
I kinda think that I can setup my application like this :
user sends a request, how? by using the application means that he/she sends out a http-request, that will load the initial state of the application
As you can see in the diagram of my desired application structure, I was able to do only the first part which is to direct the request to a single entry (index.php)
Now the problems are the initialization of other parts of the application.
As of this moment, I have 3 files that I want to setup, but I am confused on how.
index_controller, index_view, Template
class Index_controller {
private $model;
private $view;
public function __construct(){
// optional model -> $this->model = 'index'
$this->view = 'index' //
}
public function index(){
$this->load->view($this->view)
}
}
class Index_view {
private $model;
private $template;
public function __construct(Model $model = null){
$this->template = new Template('default');
}
public function view() {
$this->template->assign('css', 'default_css'); // don't know if this is efficient
// or $this->template->assign('header', 'default_header');
// or $this->template->assign('sidebar', 'default_sidebar');
// or $this->template->assign('footer', 'default_footer');
// or any other things I want to use in the template
}
}
class Template {
public $data = array();
private $tmpl;
public function __construct($template) {
$this->tmpl = $template . '_tmpl.php';
}
public function assign($name, $value){
$this->data[$name] = $value;
}
// public function output
// function that will explode the data array and render it out as a webpage
// I'll create templates and
}
With that at hand, I want to know now how do I link those things together. At the moment I have a system folder that can contain classes, and I setup a autoloader for that folder.
I am thinking of creating a Controller class and View class that acts as the ActionFactory and ViewFactory as illustrated in the diagram, although I know that these are not their responsibilities.
I am thinking of this :
class Controller {
protected $load;
public function __construct() {
$this->load = new View();
}
}
class View {
public function __construct() {
// some things i don't know
}
public function view() {
// some things i don't know
}
}
What are your suggestions and comments in my setup. How can I initiate the triad?
Well, let's not get stuck on your implementation details too much. You can go read about securing your framework at some other time. Let's deal with your question...
I actually created a framework that works along the lines you are trying to implement. I think what you are missing is a RoutingHandler class. Routing is the physical manipulation of the URL, which tells your application which Controller to load, and which Action to run.
In my world I also have Modules, so the basic routing scheme is
Module -> Controller -> Action
These three items map to my URI scheme in that fashion. Variables can be appended also like so...
http://www.domain.com/module/controller/action/var1/val1/var2/val2
So, what happens after the URI is parsed, and control is passed over to the appropriate controller and action? Let's make some code up to demonstrate a simple example...
<?php
class indexController extends Controller {
protected function Initialize() {
$this->objHomeModel = new HomeModel;
$this->objHeader = new Header();
$this->objFooter = new Footer();
$this->objHeader
->SetPageId('home');
}
public function indexAction() {
$this->objHeader->SetPageTitle('This is my page title.');
}
}
?>
In the Initialize method, I'm setting some controller-wide stuff, and grabbing an instance of my Model to use later. The real meat is in the indexAction method. This is where you would set up stuff to use in your View. For example...
public function randomAction() {
$this->_CONTROL->Append($intSomeVar, 42);
}
_CONTROL is an array of values that I manipulate and pass onto the View. The Controller class knows how to find the right template for the View because it is named after the Action (and in a sibling directory).
The Controller parent class takes the name of the action method and parses it like so...
indexAction -> index.tpl.php
You can also do some other fun stuff here, for example...
Application::SetNoRender();
...would tell the Controller not to render inside a template, but just complete the method. This is useful for those situations where you don't actually want to output anything.
Lastly, all of the controllers, models, and views live inside their own directory like so...
my_module
controllers
indexController.class.php
someotherController.class.php
:
:
models
HomeModel.class.php
:
:
templates
index.tpl.php
someother.tpl.php
:
:
I could go on, but I'm writing this from memory, and there are some wrinkles here and there, but hopefully this gives you food for thought.
I'm planning to use Mustache templates along with Kohana in my next project. So what I'm trying to do is to make Kohana seamlessly use Mustache whenever rendering a view. For example, I would have this file in my views folder:
myview.mustache
Then I can do in my application:
$view = View::factory('myview');
echo $view->render();
Just like I would do with a regular view. Does Kohana allow this kind of thing? If not, is there any way I could implement it myself using a module? (If so, what would be the best approach?)
PS: I had a look at Kostache but it uses a custom syntax, which for me is the same as using Mustache PHP directly. I'm looking to do it using Kohana's syntax.
Edit:
For information, this is how I ended up doing it, based on #erisco's answer.
The full module is now available on GitHub: Kohana-Mustache
In APPPATH/classes/view.php:
<?php defined('SYSPATH') or die('No direct script access.');
class View extends Kohana_View {
public function set_filename($file) {
$mustacheFile = Kohana::find_file('views', $file, 'mustache');
// If there's no mustache file by that name, do the default:
if ($mustacheFile === false) return Kohana_View::set_filename($file);
$this->_file = $mustacheFile;
return $this;
}
protected static function capture($kohana_view_filename, array $kohana_view_data) {
$extension = pathinfo($kohana_view_filename, PATHINFO_EXTENSION);
// If it's not a mustache file, do the default:
if ($extension != 'mustache') return Kohana_View::capture($kohana_view_filename, $kohana_view_data);
$m = new Mustache;
$fileContent = file_get_contents($kohana_view_filename);
return $m->render($fileContent, Arr::merge(View::$_global_data, $kohana_view_data));
}
}
Yes, you can. Since Kohana does some trickery with autoloading, what they dubbed "Cascading Filesystem", you can effectively redefine the functionality of core classes. This is something that Code Igniter also does, if you are familiar.
Particularly, this is the View::factory method you are referring to. Source.
public static function factory($file = NULL, array $data = NULL)
{
return new View($file, $data);
}
As you can see, this returns an instance of View. Initially, View is not defined, so PHP goes looking for it using autoloading. This is when you can take advantage of the cascading filesystem feature by defining your own View class, which must be in the file APPPATH/View.php where APPPATH is a constant defined in index.php. The specific rules are defined here.
So, since we can define our own View class, we are good to go. Specifically, we need to override View::capture, which is called by $view->render() to capture the inclusion of the template.
Take a look at the default implementation to get an idea of what to do and what is available. I've outlined the general idea.
class View
{
/**
* Captures the output that is generated when a view is included.
* The view data will be extracted to make local variables. This method
* is static to prevent object scope resolution.
*
* $output = View::capture($file, $data);
*
* #param string filename
* #param array variables
* #return string
*/
protected static function capture($kohana_view_filename, array $kohana_view_data)
{
// there
$basename = $kohana_view_filename;
// assuming this is a mustache file, construct the full file path
$mustachePath = $some_prefix . $basename . ".mustache";
if (is_file($mustachePath))
{
// the template is a mustache template, so use whatever our custom
// rendering technique is
}
else
{
// it is some other template, use the default
parent::capture($basename, $kohana_view_data);
}
}
}
I'm adding new features to an existing code base. Anyway, the current feature I'm doing should be in MVC in my opinion. The existing code base isn't MVC but the feature I'm implementing, I want it to be MVC. And I don't want to roll some existing MVC into the existing codes.
So, my problem is... I don't know to implement a render function for the controller class. Usually, in MVC you have the controller do some stuff, set it to a variable using a render function, and the View can now magically access that given variable set by the controller.
I have no idea how to do this other than global, which just feel wrong, I keep on telling myself there has to be a better way. Edit: It's global isn't it? >_> How does those other frameworks do it?
Here's a silly example:
Controller:
class UserController extend BaseController
{
public function actionIndex()
{
$User = new User; // create a instance User model
$User->getListofUser();
$this->render('ListOfUser', 'model'=>$model);
}
}
View:
<?php
//I can use $ListOfUser now...
//some for loop
echo $ListofUser[$i];
?>
Thank you in advance!
A very simple example:
class View {
function render($file, $variables = array()) {
extract($variables);
ob_start();
include $file;
$renderedView = ob_get_clean();
return $renderedView;
}
}
$view = new View();
echo $view->render('viewfile.php', array('foo' => 'bar'));
Inside viewfile.php you'll be able to use the variable $foo. Any code in the view file will have access to $this (the View instance) and any variables in scope inside the render function. extract extracts the array's contents into local variables.