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);
}
}
}
Related
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.
I have a php script that receives variables via GET method. I can access the variables using $_GET['var'], but I want to use ZF2's $this->params()->fromQuery('var') construct, without having ZF2 engine in place.
I am interested in 'how'. In case you are interested in 'why (would I want to do this)', it's because I am working on large existing legacy codebase, rewriting it to use ZF2 step by step. Next step is to use params().
I am thus interested in building up the needed code to make the params() plugin work. My thoughts are to write a trait that can be called into my class, where then inside the class I can use $this->params()->fromQuery().
My current thoughts
trait ParamTrait
{
public function params(string $param = null, mixed $default = null)
{
//magic
return $params;
}
}
class X
{
use ParamTrait;
function showGet()
{
echo $this->params()->fromQuery('var');
}
}
The magic part is what I am looking for to fill.
params() is just a controller plugin proxy to Zend\Http\Request
You can use this anywhere outside of a full ZF2 MVC app by adding zendframework/zend-http to your composer.json
require{
"zendframework/zend-http": "2.3.0"
}
include the ./vendor/autoload.php in your file
<?php
require 'vendor/autoload.php';
$request = new \Zend\Http\PhpEnvironment\Request();
//post
$post = $request->getPost();
//query
$query = $request->getQuery();
// etc, etc...
I have created a class of my own which I'm using from methods which override those in a couple of Cake's caching-related classes in order to customise the filenames of the files that CacheHelper saves. (The filenames sometimes need to include the type of user so that logged in users don't see cached versions of what non-logged in users would see and vice-versa, for example.)
My class is called MyCacheUtility and resides in app/Lib/Cache.
In this class, I need to ascertain what the controller and action are. What is the best way of doing so, and can I use Cake's CakeRequest class?
So far, I have included App::uses('CakeRequest', 'Network');, and...
I can't use $this->params('controller') as my class isn't a controller or component:
Error: Using $this when not in object context
File: /srv/www/app/Lib/Cache/MyCacheUtility.php
Line: 20
I can't use CakeRequest::param('controller') as (if I understand correctly) CakeRequest's methods are not designed to be used statically:
Error: Using $this when not in object context
File: /srv/www/cakephp/lib/Cake/Network/CakeRequest.php
Line: 864
The contents of my class:
App::uses('CakeRequest', 'Network');
class MyCacheUtility {
/**
* Sets the cache file’s filename to include references to user type
* and organisation (depending on the current controller and action)
* so that both logged-in users from different organisations and visitors
* get the right stuffs.
*/
public static function getPrefix() {
$prefix = '';
switch ('controller') . '/' . 'action') { // this is where I need the controller and action
case 'user_lists/organisation_lists':
$prefix .= MyCacheUtility::sessionDataToFilename('currentUser.organisationName');
break;
}
$prefix .= MyCacheUtility::sessionDataToFilename('currentUser.type');
return $prefix;
}
/**
*
*/
private static function sessionDataToFilename($sessionValue) {
if (CakeSession::read($sessionValue)) {
return strtolower(Inflector::slug(CakeSession::read($sessionValue))) . '-';
}
}
}
Update:
I found I wasn't able to pass the CakeRequest object from CacheDispatcher to MyCacheUtility class, and I think this is because CakeRequest has not been instantiated at the point at which CacheDispatcher does its stuff?
I do now have what I set out to do working. I'm passing Cake's $path variable through to my method and manually doing some processing based on this:
class MyCacheUtility {
public static function getPrefix($path) {
$pathParts = explode('/', parse_url($path, PHP_URL_PATH));
$prefix = '';
if (count($pathParts) > 1) {
if ($pathParts[1] == 'shared_lists') {
$prefix .= MyCacheUtility::sessionDataToFilename('currentUser.organisationName');
}
}
$prefix .= MyCacheUtility::sessionDataToFilename('currentUser.type');
return $prefix;
}
private static function sessionDataToFilename($sessionValue) {
if (CakeSession::read($sessionValue)) {
return strtolower(Inflector::slug(CakeSession::read($sessionValue))) . '-';
}
}
}
I was hoping to be able to directly apply Cake's own logic, but this seems to be doing the job okay so far.
Why do you need a library class at all? Extend the CacheHelper and use your customized cache helper via aliasing as $this->Cache in the views. To modify the file names it should be enough to override CacheHelper::_writeFile().
If you want to modify the caching itself write a new cache engine or extend an existing one to modify it's behavior. Your question does not really contain much information about why and what exactly you want to do so I can't give a better advice.
The CakeRequest object should be nearly everywhere present, so just pass it to the constructor of your cache lib. But like I said, architecturewise I doubt your approach is the best path to go. Show your cache lib code?
new MyCacheLib(CakeRequest $request);
Edit:
Write your custom Cache dispatcher or extend the existing one instead. You'll get the request object passed to it by default. Take a look at the CacheDispatcher filter. No need for a lib class here.
A more simple approach might be to write a custom cache engine and configure the helper to use that, same for the cache dispatcher filter. This way you can share the code. Or use a trait (php 5.4) to share the path building logic between the filter and the helper.
In my bootstrap.php I have many _initX() functions, and some of them may contain code that depends on code in the previous initX
protected function _initAutoloading() { }
protected function _initViewInitializer() { }
protected function _initVariables() { }
So my question, are these _init functions guaranteed to be executed in the exact order they've been declared?
EDIT - To provide a more direct answer to your question, I would say that they probably will be since the code uses ReflectionObjects::getmethods() or get_class_methods depending on your PHP version, so I believe those will return the function in order but there is nothing in the PHP docs or Zend docs that guarantee this will always be the case, so I would not consider this a supported feature.
You can pass the names of the resource functions you want/need to call as part of the bootstrap call: $bootstrap->bootstrap(array('foo', 'bar')); instead of not passing anything and let the Zend Application call them all automatically in which you are not sure of the order.
If you have dependencies in between your bootstrap resources however, I suggest you look at Resource plugins which will allow you to separate your code in different classes and easily call $bootstrap('foo') from within your 'bar' resource plugin code (though you can do so with the _init*() functions as well)
Another benefit of resource plugins is they can be shared with other bootstrap files if you need to and they are easier to test than _init*() functions.
Make sure you read theory of operation document from the Zend Application doc
If you really need them invoked in a particular order, you should use a helper list:
var $init_us = array(
"_initAutoloading",
"_initViewInitializer",
"_initVariables",
);
function __construct() {
foreach ($this->init_us as $fn) {
$this->{$fn}();
}
}
To use that construct in ZF you could rename the example __construct into _initOrderedList and your custom _initFunctions into _myinit... or something.
Read the manual. There are a section called Dependency Tracking :
If a resource depends on another resource, it should call bootstrap() within its code to ensure that resource has been executed. Subsequent calls to it will then be ignored.
Here is sample code :
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequest()
{
// Ensure the front controller is initialized
$this->bootstrap('FrontController');
// Retrieve the front controller from the bootstrap registry
$front = $this->getResource('FrontController');
$request = new Zend_Controller_Request_Http();
$request->setBaseUrl('/foo');
$front->setRequest($request);
// Ensure the request is stored in the bootstrap registry
return $request;
}
}
You don't have to rely on the order.
Is it possible in php to load a function, say from a external file to include in a class.
I'm trying to create a loader for helper functions so that I could call:
$registry->helper->load('external_helper_function_file');
after that it should be able call the function in file like this:
$registry->helper->function();
Thanks for any help
Setting aside opinions it it's good OOP design. It's possible even with current version of PHP, although not as clean, as it can be with PHP5.3.
class Helper {
/* ... */
function load($file) {
include_once($file);
}
function __call($functionName, $args) {
if(function_exists($functionName))
return call_user_func_array($functionName, $args);
}
}
ok, 1st, i agree that this is bad manners. also, in 5.3, you could use the new closure syntax with the __call magic word to use operators as functions (JS style).
now, if we want to supply a way of doing this you way, i can think of using create_fnuction, mixed with the __call magic.
basically, you use a regex pattern to get convert the functions into compatible strings, and put themin a private member. than you use the __call method to fetch them. i'm working on a small demo.
ok, here is the class. i got the inspiration from a class i saw a few weeks ago that used closures to implement JS-style objects:
/**
* supplies an interface with which you can load external functions into an existing object
*
* the functions supplied to this class will recive the classes referance as a first argument, and as
* a second argument they will recive an array of supplied arguments.
*
* #author arieh glazer <arieh.glazer#gmail.com>
* #license MIT like
*/
class Function_Loader{
/**
* #param array holder of genarated functions
* #access protected
*/
protected $_funcs = array();
/**
* loads functions for an external file into the object
*
* a note- the file must not contain php tags.
*
* #param string $source a file's loaction
*
* #access public
*/
public function load($source){
$ptrn = '/function[\s]+([a-zA-Z0-9_-]*)[\s]*\((.*)\)[\s]*{([\w\s\D]+)}[\s]*/iU';
$source = file_get_contents($source);
preg_match_all($ptrn,$source,$matches);
$names = $matches[1];
$vars = $matches[2];
$funcs = $matches[3];
for ($i=0,$l=count($names);$i<$l;$i++){
$this->_funcs[$names[$i]] = create_function($vars[$i],$funcs[$i]);
}
}
public function __call($name,$args){
if (isset($this->_funcs[$name])) $this->_funcs[$name]($this,$args);
else throw new Exception("No Such Method $name");
}
}
limitations- 1st, the source cannot have any php tags. 2nd, functions will always be public. 3rd- we can only mimic $this. what i did was to pass as a 1st argument $this, and the second is the array of arguments (which is a 4th limition). also, you will not be able to access non-public members and methods from within the class.
an example for a source file:
function a($self,$arr=array()){
//assuming the object has a member called str
echo $self->str;
}
this was a fun exercise for me, but a bad practice all in all
So you want to not just include a file, but include it into an object's scope?
...
I think you're going about this the wrong way. It makes more sense if the registry object has a series of helper members, which have functions of their own. The net result might look something like this:
$registry->aHelper->aFunction();
$registry->aDifferentHelper->aDifferentFunction();
With careful use of {} syntax, you should be able to dynamically add member objects to your god object.
At this point it's worth noting that a god object is almost invariable an anti-pattern. If you need those functions globally, use a bootstrapping include technique and put then in global scope. If you need to pass that data around, then either pass it to functions as required or store it in a database and retrieve it elsewhere.
I know that god object really looks like a good idea, but I promise you it will make things a mess later on.