Yii: Theme support for components? - php

I was wondering if yii components are also supporting the theme feature? In my environment right now a component is only considering files within the component/views/ folder.
Now that I am also using themes it would be nice to tell the component to look for the view under the themes/themeName/ folder.
Using the method below I can work around this but it certainly doesn't feel like this is the yii-way to do it.
protected function renderContent()
{
$view = './../../../themes/'.Yii::app()->theme->name.'/views/viewName';
$this->render($view);
}
Do you know of a more elegant solution to achieve this?

There isn't any theming on components. Mainly because they're not intended to be rendering content for anything. Nothing wrong with that though, sometimes it's required.
Easiest solution is probably to just make it more readable, using path aliases always helps:
protected function renderContent()
{
$view = 'webroot.themes.'.Yii::app()->theme->name.'.views.viewName';
$this->render($view);
}
Or you could add a method the component, or extend CComponent to get it across all components if you want it:
public function getViewsPath(){
return 'webroot.themes.'.Yii::app()->theme->name.'.views';
}
Or you could set a path alias:
Yii::setPathOfAlias('theme','webroot.themes.'.Yii::app()->theme->name);
Then you could use that anywhere in your application provided you run it at an early enough point in the process.

Related

CakePhp 3.x non-default view file extension

I moved from CakePhp 2.x to CakePhp 3.x
In CakePhp 2.x, in my ApiController I could do something like this:
public function getScript(){
$this->layout = 'empty';
$this->ext = '.js';
}
The above code would have rendered the following view:
View/Api/get_script.js
(please note the .js extension)
How can I implement the same behaviour using CakePhp 3.x?
I read in the documentation that:
The Controller::$ext property has been removed. You now have to extend and override the View::$_ext property if you want to use a non-default view file extension.
But I don't understand how and where to extend the View::$_ext property
I tried the following:
I created a new View class in src/View/ApiView.php
namespace App\View;
use Cake\View\View;
class ApiView extends View
{
protected $_ext = '.js';
}
And in my controller:
public function getScript(){
$this->viewClass='Api';
$this->layout = 'ajax';
}
But now I get the following error :
Error: The layout file Layout/ajax.js can not be found or does not
exist.
And this makes perfect sense since I told CakePhp to use .js as the default extension. But I would like to use the .ctp extension for the layout and the .js extension for the template view.
It was so simple in CakePHP 2.x I believe there must be an easy solution in CakePHP3.x too.. please help!
Thank you
You can't have your cake and eat it (too)! The $_ext property is being used for all template types (actions, elements, layouts), and its usage is buried in various different places, so changing this to be used selectively is a little tedious.
Your best bet would probably be to override View::_getViewFileName() and change the extension temporarily, that's not very nice, but the least intrusive solution that comes to my mind right now:
protected function _getViewFileName($name = null)
{
$oldExt = $this->_ext;
$this->_ext = '.js';
$filename = parent::_getViewFileName($name);
$this->_ext = $oldExt;
return $filename;
}
Instead of using template extensions you might want to look at using RequestHandlerComponent to do content-type negotiation. This would let you use directories like Template/Api/js/action.ctp. I find this helps keep the various response formats for an API clean as controllers with many actions don't end up with sprawling list of view files.
To answer your original question, you can't change the extension for only the template, and not the layout with the default view class. You would need a custom view class if you want that kind of behavior. Take a look at _getViewFileName and _getLayoutFileName for which methods to override in a subclass.

Symfony DI Extensions - add custom DoctrineExtension

There is a DoctrineExtension in the
Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension
package. I cant find where it is defined in the framework and how do I replace this class by my own. I want to change some behavior of this extension.
The file itself is located under: vendor/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle/DependencyInjection
I think to change it you will probably have to extend DoctrineBundle with:
public function getContainerExtension()
{
return new MyDoctrineExtension();
}
Have not tried it myself. I expect there will be plenty of other issues with trying to change such a core file. But maybe not. Let us know if it works. Might come in handy.

PHP class and method overriding - implement callbacks

I'm currently working on a project where the core system is distributed out to many different clients - and then should the client request changes, we have to make them individually on each system which means that eventually the core code varies from client to client, and keeping it up to date and copying new features across the system is difficult.
I have proposed we move to (what I'm calling) an 'override model' which has an outside skeleton structure of the code. Somewhat like:
|- controllers
|- models
|- views
|- core
|- controllers
|- Controller1.php
|- models
|- views
If you then wanted to make changes to Controller1.php, you would copy it into the outside structure and make changes - an autoloader would then load the appropriate files if they exist by checking the Skeleton structure for them first, i.e.
Loader::controller('Controller1');
However I wondered if it is possible to go a bit further than that - its all well and good overriding the Controller if changes are needed, but then any future core additions or fixes might not get added in. So I thought you could possibly create a copy of the file and override just the singular method calls. An semi-example of what I mean is as follows:
class Override {
public function __call($method, $args) {
return call_user_func_array(array('Something', $method), $args);
}
public static function __callStatic($method, $args){
return call_user_func_array(array('Something', $method), $args);
}
}
// Core class
class Something {
static function doTest() {
echo "Class something <br/>";
}
static function doOtherTest() {
echo "That works <br/>";
self::doTest();
}
}
// Overriding class - named differently for ease of example and reasons explained later
class SomethingElse extends Override {
private static function doTest() {
echo "Success <br/>";
}
}
// Actual function calling
SomethingElse::doTest();
SomethingElse::doOtherTest();
The general idea being that if the method doesn't exist in the originating class, then action it from the 'parent' class (which is hardcoded here). However I have two issues with this method:
I think I will run into trouble when the classes have the same names
If I am attempting to override a method that the parent class subsequently calls, it will use it's own version of the method as opposed to the one I am attempting to override
Although the 'simple' solution is to say you should override any methods that are in conjunction, but more might be added at a later date.
Currently I am trying to just do the initial solution of full class overriding using the loader, which works and is less complex.
However I wondered if any of the great minds on StackOverflow might know of any answer or set-up that might help address the issues with the method overriding idea - please bear in mind I'm working with an existing system set up, although the skeleton structure idea is what I am trying to implement to bring some form of 'control' over what is changed. Ideally, nothing in the core would change (at least not by much) when someone wants to override a method or similar.
Well we've just solved it. Traits it is!
But seriously by converting the versioned code to traits and then calling them in the non versioned files in the above structure. This then negates the need for a loader class and other clash prevention layers and allows the core code to be updated, tested and committed without affecting custom code per client.
well the strict OO Kind of Solution would surely be to spread your controller as abstract interfaces that could be implementet by several different real world approaches and then brought together by the composition over inheritace principle.
As i understand you've got existing code here that you intend to override or extend. If your PHP-Version allows you to use Traits, this might help you too:
PHP 5.4: why can classes override trait methods with a different signature?

How to make Zend automatically switch view and layout with contexts?

I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.
Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin
ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.
I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png

Best practices for managing several specialized versions of one app

I have a web application that has many faces and so far I've implemented this through creating themes. A theme is a set of html, css and images to be used with the common back end.
Things are laid out like so:
code/
themes/theme1
themes/theme2
And each instance of the web application has a configuration file that states which theme should be used. Example:
theme="theme1"
Now new business rules are asking me to make changes to certain themes that can't be achieved through simply change the html/css/images and require changing the backend. In some cases these changes need to be applied to a group of themes.
I'm wondering how to best lay this out on disk, and also how to handle it in code. I'm sure someone else must have come up against this.
One idea is to have:
code/common
code/theme1
code/theme2
themes/theme1
themes/theme2
Then have my common code set the include_path such that code/theme1 is searched first, then code/common.
Then if I want to specialize say the LogoutPage class for theme2, I can simply copy the page from code/common to the same path under code/theme2 and it will pick up the specialized version.
One problem with this idea is that there'll be multiple classes with the same name. Although in theory they would never be included in the same execution, I wouldn't be able to extend the original base class.
So what if I was to make a unique name for the base class? e.g. Theme1LogoutPage extends LogoutPage. One problem I can foresee with that is when some common code (say the Dispatcher) references LogoutPage. I can add conditions to the dispatcher, but I wonder if there's a more transparent way to handle this?
Another option I can think of is to maintain separate branches for each theme, but I think this could be a lot of work.
One final thing to consider is that features might originate in one theme and then require merging into the common codebase.
Any input greatly appreciated. If it makes any difference, it's a LAMP environment.
I don't have a specific recommendation. However, I strongly suggest to NOT take shortcut... Use the solution that will you will find comfortable to add a third theme or to change something next year.
Duplication is the enemy of maintainability.
I'd investigate using the Strategy pattern as a means to implement different functionality in different versions of the site. Have a Factory that takes in your configuration and supplies the appropriate code strategy based on it. Each strategy can implement some common interface so that they are interchangeable from the calling class' point of view. This will isolate your changes to implement new strategies to the Factory class, Configuration class, and any new strategy classes that you need to implement to make the change. You could do the same (or similar) with any user controls that need to differ between the different versions.
I'll illustrate with pseudocode (that may look suspiciously like C#)
public interface ILogoutStrategy
{
void Logout();
}
public abstract class AbstractLogoutStrategy : ILogoutStrategy
{
public virtual void Logout()
{
// kill the sesssion
}
}
public class SingleSiteLogoutStrategy : AbstractLogoutStrategy
{
public void Logout()
{
base.Logout();
// redirect somewhere
}
}
public class CentralAuthenticationSystemLogoutStrategy : AbstractLogoutStrategy
{
public void Logout()
{
base.Logout();
// send a logout request to the CAS
// redirect somewhere
}
}
public static class StrategyFactory
{
public ILogoutStrategy GetLogoutStrategy(Configuration config)
{
switch (config.Mode)
{
case Mode.CAS:
return new CentralAuthenticationSystemLogoutStrategy();
break;
default:
case Mode.SingleSite:
return new SingleSiteLogoutStrategy();
break;
}
}
}
Example usage:
ILogoutStrategy logoutStrategy = StrategyFactory.GetLogoutStrategy( config );
logoutStrategy.Logout();
Are you using Master Pages? If you need different layout and UI stuff you could just have a different set of master pages for each of your instances. If you need custom behavior then you might want to look into Dependency Injection. Spring.NET, etc.
What you need are templates.
Thence you can separate your code from your presentation.
I highly recommend smarty templates. Also PEAR template_it.
http://www.smarty.net/
This also make your code far more maintainable. The aim is to have no html in your php, and to have no php in your html.
then all you will need to do is change the html template that is being used for each theme. or folder of templates.
You could have:
/common/code
And:
/sitename/code
All files in /common/code are abstract classes.
For every file in /common/code, just create a corresponding non-abstract class file in /sitename/code that INHERITS from the abstract class in /common/code.
This way you only need to implement CHANGES in the /sitename/code
Everything else is core functionality that exists only in /common/code
The important thing to do here is ensure that you only add public methods to the abstract classes. This way the methods are available to all sites, and classes from all sites can be treated/worked with identically.
I would do:
[theme name]/[subfolder]
default/common
default/common/html
default/common/css
red/code
red/common
red/common/html
red/common/css
red/code
green/common
green/common/html
So if the code or any other component doesn't exist it will fall back to default.
But in fact I would branch the website in svn, so common code if it evolves I can merge it, etc.. see Subversion at: http://subversion.tigris.org/

Categories