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.
Related
Since my app is getting bigger i would like to separate admin prefix actions and views from normal actions and views. The new folder for admin is Controller/admin/UsersController.php.
I would like to change my cakephp controllers and views folder structure to match the prefix I'm using.
Example for admin prefix:
Controller:
app/Controller/UsersController.php (contain view(), index() ...)
app/Controller/admin/UsersController.php (contain admin_view(), admin_index() ...)
View:
app/View/Users/index.ctp (for index() in UsersController.php)
app/View/Users/admin/index.ctp (for admin_index() in admin/UsersController.php)
How can I implement this structure using Cakephp 2.6?
Unlike in 3.x where this is the default behavior for prefixes, this isn't supported in 2.x.
You could try hacking it in using a custom/extended dispatcher (in order to retrieve the desired controller) or even dispatcher filters in case you are adventurous, and in your app controller modify the view template path with respect to the prefix.
That should do it, however I would probably simply go for using plugins instead, this will separate things just fine without any additional fiddling.
If you just want to separate logic you could do something like this. It's untested an just thought to give you just the idea. I'll explain the concept after the code:
public function beforeFilter() {
if ($this->request->prefix === 'foo') {
$name = Inflector::classify($this->request->prefix);
$className = $name . 'ChildController';
App::uses($className, 'Controller/Foo');
$this->ChildController = new $className($this);
}
}
public function __call($method, $args) {
if ($this->request->prefix === 'foo' && method_exists($this->ChildController, $method)) {
call_user_func_array([$this->ChildController, $method], $args);
}
}
Depending on the prefix you can load other classes. How you load that class and how you instantiate it, what params you pass to it is up to you. In my example I'm passing the controller instance directly. I think you could actually init a complete controller here but be aware that components like the Session might cause a problem because they might have been already initiated by the "parent" controller.
When you now call a controller method that doesn't exist it will try to call the same method with the same arguments on the ChildController. Not really a great name for it, but maybe you can come up with something better.
You'll have to implement some logic to load the views from the right place in your classes as well but this shouldn't be hard, just check the controller class.
But actually I don't see your problem, I've worked on an app that hat over 560 tables and not putting the code into sub folders wasn't a problem, it did in fact use a similar solution.
I think you have to much code in your controllers, get more code into your models and the controller shouldn't be a problem.
Another solution might be to think about implementing a service layer in CakePHP which implements the actual business logic while a model is reduced to a data handler. The service would sit between a controller and the model. I've done this a few times as well now and if done right it works very well.
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.
I got a PHP library (PHP Markdown) in my library fold in an standard Zend Framework application. What is the best way to load the file and all it's classes to use in my models and controllers.
Structure:
library/phpMarkdown/markdown.php
Note:
PHP Markdown has a really ugly structure: It's only real "API" is a simple function, not a class. So the elegant was do not work for this exact case, but regarding the question the genearl solution the correctly named files/class is also "the right answer.
Edit
So much good input here, really not sure which answer I should accept! Thanks to you all!
The autoloader
Just instantiate the class and the autoloader should find it. If it doesn't you need to add the namespace and path.
If you have a class in the following tree (for exemple) : library/My/Tool.php
You will need to add this to your application.ini :
autoloaderNamespaces[] = "My_"
And then in your code you just call :
$tool = new My_Tool();
Edit :
in the file Tool.php you must follow the Zend Naming Conventions and have something like this :
<?php
class My_Tool {
}
For more informations see this : Zend Naming conventions
To keep it simple and just add that one file, you could put something like this in your Bootstrap.php:
protected function _initLoad(){
Zend_Loader::loadFile('markdown.php', '/../library');
}
I just copied markdown.php into the application library and put this little function in the bootstrap. You could also use Zend_Loader::loadClass(); if you want.
In my Symfony 1.4 project, until now, here is how I used i18n setflash messages in my actions :
$message = $this->getContext()->getI18N()->__('message’);
$this->getUser()->setFlash('notice', $message);
but I've just found a simplest way. Here the code I use in my projectconfiguration class :
public function setup()
{
$this->loadHelpers(array('I18N'));
}
and then here the code I use in my actions :
$this->getUser()->setFlash('error', __('message'));
for me, it is very convenient because I don't have to use the getContext->getI18N everywhere but I would like to know if there is a drawback using this method ?
I always use this method in many projects and I didn't see any drawbacks.
Helpers are here to give us a library of useful functions. Some helper's functions return html so (impov) they are dedicated to the template, but in case of I18N or Date, it can be useful to have them in an action.
And if you don't want to load your helper in a global view, you can use:
sfApplicationConfiguration::getActive()->loadHelpers(array('I18N'));
I was wondering where is the correct location to place the App::import on CakePHP2.
I was thinking that it should be better to use it in each function in order not to load if another function doesnt use it.
Something like this:
public function name(){
App::import('Controller', 'Classifiers');
$classifiersController = new ClassifiersController();
$this->request->data['Post'] = $classifiersController->getIdCategory('hola');
}
Instead of using the import at the top of the class.
What do you think?
the correct place for app::import: no where! ;)
you use App::uses() in 2.0 for all app classes (import is only for vendor stuff).
and you would place it at the very top of your file (after the <?php)
in your case:
<?php
App::uses('ClassifiersController', 'Controller');
...
public function name(){
$classifiersController = new ClassifiersController();
$this->request->data['Post'] = $classifiersController->getIdCategory('hola');
}
although I HIGHLY recommend to take a closer look at what you are doing there.
using another controller in a controller is pretty wrong - in your case you would probably want to import a model and use its method. controllers are only for logic of a specific request action. place all other things in the model (fat model, slim controller principle).