Is there any way to pass through a secondary path to the views dir in phalcon?
in zend framework I think the syntax is
$this->view->addScriptPath('/backup/path');
$this->view->addScriptPath('/preferred/path');
so if there is a file in the preferred path it will use it, if not it will fallback through the chain.
I use this, for example, for mobile versions when most of the pages are the same, but some have to be significantly different and I don't want to have to duplicate all the views just for 2 or 3 variants
In phalcon I have tried sending an array to the view, but that just results in neither working
$di->set('view', function() use ($config) {
$view = new \Phalcon\Mvc\View();
$view->setViewsDir( array('/preferred/path/', '/backup/path/') );
return $view;
});
I've got this working by extending the Phalcon\Mvc\View\Engine\Volt
In the render($template_path, $params, $must_clean = null) method I set the alternative path, check if file is available and if so I switch the $template_path given with the alternative path. Then it's just a case of calling:
return parent::render($template_path, $params, $must_clean);
where $template_path contains the new (alternative) path.
If your alternative path might change on a per project basis and you need to set it in bootstrap, then rather than doing it when getting a "view" from di you would do it when getting volt.
Just remember that all views are rendered with that method so you will have to account for layout and partial views as well - depending on your implementation.
Example: (this has not been tested, it's based on a similar set up I have in my own code)
<?php
class Volt extends Phalcon\Mvc\View\Engine\Volt
{
private $skin_path;
public function render($template_path, $params, $must_clean = null)
{
$skin_template = str_replace(
$this->di->getView()->getViewsDir(),
$this->getSkinPath(),
$template_path
);
if (is_readable($skin_template)) {
$template_path = $skin_template;
}
return parent::render($template_path, $params, $must_clean);
}
public function setSkinPath($data)
{
$this->skin_path = $data;
}
public function getSkinPath()
{
return $this->skin_path;
}
}
In your bootstrap:
$di->setShared('volt', function($view, $di) {
$volt = new Volt($view, $di);
$volt->setSkinPath('my/alternative/dir/');
return $volt;
});
Many thanks to nickolasgregory#github who pointed me in the right direction.
Method proposed by #strayobject helps me also, but I've found that using extend or other statements inside volt templates dosn't work.
Here's refined solution that works with extend and include:
use Phalcon\Mvc\View\Engine\Volt;
class VoltExtension extends Volt
{
// Override default Volt getCompiler method
public function getCompiler()
{
if (!$this->_compiler) {
$this->_compiler = new VoltCompilerExtension($this->getView());
$this->_compiler->setOptions($this->getOptions());
$this->_compiler->setDI($this->getDI());
}
return $this->_compiler;
}
}
And
use Phalcon\Mvc\View\Engine\Volt;
class VoltCompilerExtension extends Volt\Compiler
{
public function compileFile($path, $compiledPath, $extendsMode = null)
{
$skinPath = $this->getOption('skinPath');
if ($skinPath) {
$skinTemplate = str_replace(
$this->getDI()->getView()->getViewsDir(),
$skinPath,
$path
);
if (is_readable($skinTemplate)) {
$path = $skinTemplate;
}
}
return parent::compileFile($path, $compiledPath, $extendsMode);
}
}
Usage:
$volt = new VoltExtension($view, $di);
$volt->setOptions(
array(
'compiledPath' => $config->application->cacheDir,
'compiledSeparator' => '_',
'compileAlways' => false,
'skinPath' => $config->application->skinPath
)
);
Please take a look at this phalcon framework update. It provides support for multiple view packages per website (you can have multiple websites). Users of the magento framework will find it easy to use:
https://github.com/alanbarber111/cloud-phalcon-skeleton
Related
I have an old project I'm working on using Slim version 2. I can not upgrade to 3.
I'm trying to integrate twig into slim 2 while also keeping the old default slim2 renderer.
Currently I have this.
class TwigView extends \Slim\View
{
public function rendertwig($template,$data = array()){
global $twig;
$twigResults = $twig->render($template,array('test' => '1'));
$data = array_merge($this->data->all(), $data);
return $this->render($twigResults, $data);
}
}
$view = new TwigView();
$config['view'] = $view; //#JA - This command overides the default render method.
//#JA - Intialize Slim
$app = new \Slim\Slim($config);
The idea is that I would call this saying $app->view->rendertwig('file.twig') when I need to render the twig templates and use $app->render('template.php') for all the other templates that use the default slim2 method of templating.
However, I get an error because in my rendertwig function $this->render() function requires a template name for the first parameter. Is there a way I can render directly the results from twig into the slim engine without needing a template file?
I'm aware this is bad form to have two templating engines but eventually I will switch everything to Twig but I need this as a temporary solution till I can patch everything over.
When I inspected slim's view object it has this defined as its render method which will explain the issue.
protected function render($template, $data = null)
{
$templatePathname = $this->getTemplatePathname($template);
if (!is_file($templatePathname)) {
throw new \RuntimeException("View cannot render `$template` because the template does not exist");
}
$data = array_merge($this->data->all(), (array) $data);
extract($data);
ob_start();
require $templatePathname;
return ob_get_clean();
}
I don't know if this is bad form but I did this as a temporary solution.
class TwigView extends \Slim\View
{
public function rendertwig($template,$data = array()){
global $twig;
$twigResults = $twig->render($template,array('test' => '1'));
echo $twigResults;
}
}
I saw that all the render method did was just require the template so I figured its safe to just echo the results from the twig templating engine? This seemed to work from my test.
I've just started looking into Zend framework 2 .One thing that I can’t seem to figure out is how to change the behavior of the framework when its deciding what view template to use when i’m not passing it in the viewmodel.
When looking for the answer myself I found the following, which states that Zend resolves view templates using the pathing below:
{normalized-module-name}/{normalized-controller-name}/{normalized-action-name}
(Source: http://zend-framework-community.634137.n4.nabble.com/Question-regarding-template-path-stack-tp4660952p4660959.html)
Now I’m looking to edit or remove the normalized-module-name segment. All the view files stay in my module/views folder. The reason I want to change this is because I’m using sub namespaces as my module name, resulting in the first segment of the namespace as the normalized module name (which is not specific enough for me).
To give you an example, the module Foo\Bar will result in an example view being rendered from:
/modules/Foo/Bar/view/foo/test/index.phtml.
I would like to change that default behavior to:
/modules/Foo/Bar/view/bar/test/index.phtml
Starting with zf 2.3 you can use extra config parameter view_manager['controller_map'] to enable different template name resolving.
Look at this PR for more info: https://github.com/zendframework/zf2/pull/5670
'view_manager' => array(
'controller_map' => array(
'Foo\Bar' => true,
),
);
Will result in controller FQCN starting with 'Foo\Bar' to be resolved following those rules:
strip \Controller\ namespace
strip trailing Controller in classname
inflect CamelCase to dash
replace namespace separator with slash
Eg: Foo\Bar\Controller\Baz\TestController -> foo/bar/baz/test/actionname
Update:
Starting with zend-mvc v3.0 this is default behavior
I had a similar problem and here's my solution.
Default template injector is attached to an event manager of the current controller with priority -90, and it resolves a template name only if a view model is not provided with one.
Knowing this, you can create your own template injector with a required logic and attach it to the event manager with the higher priority.
Please see the code below:
public function onBootstrap(EventInterface $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->getSharedManager()
->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH,
new TemplateInjector(),
-80 // you can put here any negative number higher -90
);
}
Your template injector which resolves template paths instead of the default one.
class TemplateInjector
{
public function __invoke(MvcEvent $event)
{
$model = $event->getResult();
if (!$model instanceof ViewModel)
{
return;
}
$controller = $event->getTarget();
if ($model->getTemplate())
{
return ;
}
if (!is_object($controller))
{
return;
}
$namespace = explode('\\', ltrim(get_class($controller), '\\'));
$controllerClass = array_pop($namespace);
array_pop($namespace); //taking out the folder with controllers
array_shift($namespace); //taking out the company namespace
$moduleName = implode('/', $namespace);
$controller = substr($controllerClass, 0, strlen($controllerClass) - strlen('Controller'));
$action = $event->getRouteMatch()->getParam('action');
$model->setTemplate(strtolower($moduleName.'/'.$controller.'/'.$action));
}
}
Here's the link from my blog where I wrote about it in more details: http://blog.igorvorobiov.com/2014/10/18/creating-a-custom-template-injector-to-deal-with-sub-namespaces-in-zend-framework-2/
Right template to ViewModel is injected in MVC event 'dispatch' (defined in ViewManager) by Zend\Mvc\View\Http\InjectTemplateListener with priority -90
You'll have to create custom InjectTemplateListener and register it with higher priority to same event.
But I'd recommend to set template in every action by hand, because of performance - see http://samminds.com/2012/11/zf2-performance-quicktipp-1-viewmodels/
template name resolving is a heavy process(on system resources), and all the articles about ZF2 performance says that you should provide the template name manually to increase performance.
so don't waste time finding a way to do something that you will end up doing manually :D
In order to improve Next Developer answer, I write the following code in TemplateInjector.php:
class TemplateInjector
{
public function __invoke(MvcEvent $event)
{
$model = $event->getResult();
if (!$model instanceof ViewModel) {
return;
}
if ($model->getTemplate()) {
return;
}
$controller = $event->getTarget();
if (!is_object($controller)) {
return;
}
$splitNamespace = preg_split('/[\\\]+/', ltrim(get_class($controller), '\\'));
$moduleName = $splitNamespace[1];
$controller = $splitNamespace[0];
$action = $event->getRouteMatch()->getParam('action');
$model->setTemplate(strtolower($moduleName . '/' . $controller . '/' . $action));
}
}
I've changed the way to build the Template path. Using regexp is faster than using array methods.
I'm building a toy app in Lithium (PHP framework) based upon the Union of RAD's Framework project. It's all working great in the browser but when running integration tests, routes.php is not loaded, so the routing isn't working.
Here's the code I'm testing:
class StaffController extends \lithium\action\Controller {
public function add() {
$staff = Staff::create();
if (($this->request->data) && $staff->save($this->request->data)) {
return $this->redirect(array('Staff::view', 'args' => array($staff->id)));
}
return compact('staff');
}
My test:
public function testAdd() {
//Router::connect('/{:controller}/{:action}/{:args}');
$request = new Request();
$request->data = array('name' => 'Brand new user');
$controller = new StaffController(array('request' => $request));
/* #var $response \lithium\action\Response */
$response = $controller->add();
$this->assertEqual(302, $response->status['code']);
}
Notice the commented out line - Router::connect('/{:controller}/{:action}/{:args}'); - if I uncomment that, it's all good.
What I'm puzzled about is why, when running in unit tests, app/config/routes.php (where I define my routes) isn't loaded. From what I can determine, app/config/bootstrap/action.php adds a filter to the "run" method of the Dispatcher which loads routes.php.
Of course, it's possible that I am totally missing the point here! I'd appreciate any guidance you can give me!
Lithium has a lithium\action\Dispatcher used for http requests and a lithium\console\Dispatcher for console commands.
I'm assuming you are running tests from the command-line. I'm looking at the "framework" project's app/config/bootstrap/action.php file (here on github).
It is only including the routes.php file for the lithium\action\Dispatcher which is not loaded from the command-line. The app/config/bootstrap/console.php also doesn't include routes.php for the console.
My suggestion is to edit the console.php file and change the filter to look like this:
Dispatcher::applyFilter('run', function($self, $params, $chain) {
Environment::set($params['request']);
foreach (array_reverse(Libraries::get()) as $name => $config) {
if ($name === 'lithium') {
continue;
}
$file = "{$config['path']}/config/routes.php";
file_exists($file) ? call_user_func(function() use ($file) { include $file; }) : null;
}
return $chain->next($self, $params, $chain);
});
SOLVED Thanks to #JPR and tuned-up thanks to #PeterM
/* The only dependance is when */ class NuSoap extends CApplicationComponent
v-Below, the initial question -v
I would like to know how to create a basic extenstion in yii 1.1.13 using nusoap 0.9.5?
My simple code looks like this :
<?php
require("libs/nusoap-0.9.5/lib/nusoap.php");
// namespace
$ns = "https://my-namespace-site";
// client
$client = new soapclient('https://ip-to-webservice-server');
// header
$headers = "<credentials><ns1:username xmlns:ns1=\"$ns\">username</ns1:username>
<ns2:password xmlns:ns2=\"$ns\">password</ns2:password></credentials>";
$client->setHeaders($headers);
// searching
$params = array(
'local_user_array' => array(
'limit' => 10
)
);
$result = $client->call('local_users_search', $params, $ns );
if( $client->getError() ) {
echo $client->getError();
}
else {
foreach( $result['data'] as $offer ) {
echo "<div>".$offer['firstname']."</div>";
}
}
?>
My code works perfectly. Now, what coud I do to use $result in yii to be able to show results in a view?
The best answer will be a concrete example with file structure and code plus meaningful explanations.
Any help would be greatly appreciated. Thanks for your help in advance. I'm looking forward to it ;-)
PS: please do not reference any links to yiiframework site because it doesn't help much as I also know how to search.
Make a class that extends from CApplicationComponent.
class NuSoap extends CApplicationComponent
{
protected $params = array();
protected $client, $ns;
public function init() {
require("libs/nusoap-0.9.5/lib/nusoap.php");
$this->client = new soapclient('https://ip-to-webservice-server');
$this->ns = "https://my-namespace-site";
}
public function getResults() {
$results = $this->client->call(
'local_users_search',
$this->params,
$this->ns
);
return $results;
}
public function setParams(array $params) {
$this->params = $params;
}
// whatever other methods you need for it to work
}
Then in your main config file, under the components array:
array(
'nuSoap' => array(
'class' => 'application.components.NuSoap' // name your class NuSoap.php
)
......
)
Make sure the application/components or application/extensions directory is imported in the main.php config file as well. Put your class file in NuSoap.php in the application/components or applcation/extensions directory.
You can now refer to your component anywhere in your Yii app:
Yii::app()->nuSoap->setParams($params);
$results = Yii::app()->nuSoap->getResults();
This should be plenty to get you started in the right direction. The Yii documentation would be very helpful in understanding how application components work, but since you don't want to read it you'll just have to guess on some things. If you want to use Yii it makes absolutely no sense to avoid reading the documentation.
dir:
application
-controllers
-models
-views
-mobile_views
How do I auto load templates at mobile_views when I use $this->load->view and view by iphone or other mobile phone?
Check this
You can do it in two way.
Way 1: Its very simple. In the above answer (the link I have given) add following line in the end of MyController function
$this->load->_ci_view_path . = $this->view_type .'/';
You are done. You can simply load view like normal view load.
Way 2:
To autoload a view based on user agent, I think you can implement it using hooks. To implement this hooks you need to follow the following steps
Autoload user agent library in autoload.php
$autoload['libraries'] = array('user_agent');
Enable hooks in config.php
$config['enable_hooks'] = TRUE;
Not implement hooks on post_controller_constructor. Add following codes to hooks.php
$hook['post_controller_constructor'][] = array('class' => 'Loadview',
'function' => 'load',
'filename' => 'loadview.php',
'filepath' => 'hooks'
);
Now create a page named loadview.php under hooks directory having following code
class Loadview
{
public static $MOBILE_PLATFORM = 'mobile';
public static $DEFAULT_PLATFORM = 'default';
public function load(){
$this->CI =& get_instance();
$view_type = $this->CI->agent->is_mobile() ? self::$MOBILE_PLATFORM : self::$DEFAULT_PLATFORM;
$this->CI->load->_ci_view_path = $this->CI->load->_ci_view_path . $view_type .'/';
}
}
You are done now. You can simply load view like normal view load.
to load views from another dir aside from "views", i found this forum topic to be helpful
http://codeigniter.com/forums/viewthread/132960/
function external_view($path, $view, $vars = array(), $return = FALSE)
{
$full_path = $path.$view.'.php';
if (file_exists($full_path))
{
return $this->_ci_load(array('_ci_path' => $full_path, '_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
}
else
{
show_error('Unable to load the requested module template file: '.$view);
}
}
and you can work the rest from the controller.
I do this in my controller:
public function index()
{
if($this->agent->is_mobile())
{
$this->load_mobile();
}
else
{
$this->load_web();
}
}
public function load_mobile()
{
$this->load->view('mobile/home');
}
public function load_web()
{
$this->load->view('web/home');
}
In this way I can add different data to mobile and to web pages.
I also extend the default controller and add some useful extra features:
Enables the usage of master page/templates.
Can add css and javascript files.
Uses the _output method for controlling the controllers output.
Can load relative content with in the form of modules (views)
So I can manage better the different pages.
Bye!!