Reusable code for controller in Cakephp3 - php

I would like to create reusable code in controller in "Cakephp way". I would like to replace always one field in few controllers before render website. For example I would like to replace string in field "body". I can do this like this in show method:
public function show($id = null) {
$site = $this->Sites->findById($id)->first();
$new_value = 'test2';
$site['body'] = str_replace('test', $new_value, $site['body']);
}
Is there any better way to do this in cakephp way for example in initalize method or beforeRender? I can't use behavior here.
EDIT:
I know about components, but how to use it to replace all $site['body] (in my code) for all controller methods (so I would like to do this automatic, like behavior for entity)?

Read about Components.
Components are packages of logic that are shared between controllers. CakePHP comes with a fantastic set of core components you can use to aid in various common tasks. You can also create your own components. If you find yourself wanting to copy and paste things between controllers, you should consider creating your own component to contain the functionality. Creating components keeps controller code clean and allows you to reuse code between different controllers.
And see Component Callbacks.

You can use component
https://book.cakephp.org/3.0/en/controllers/components.html
Don't forget to load it in appController or where your need it
After edit :
#nexequ
Maybe if you set the beforeRender in your appController
public function beforeRender()
{
debug($this->request);
}
In $this->request->data array you have your data to replace.
Exemple:
data => array(
'Reunion' => array(
'begin' => '2017-01-13 20:00:00',
'end' => '2017-01-13 20:30:00'
)
If you find the way to get the model ("Reunion" in my example.)
You can do a trick like
replace --> $this->request->data[$model]['body']

I found solution with burzum help, I can use virtual property in src/Model/Entity:
protected function _getBody() {
$new_value = 'test2';
$test = str_replace('test2', $new_value, $this->_properties['body']);
return $test;
}
It will replace for instance 'test2' with $new_value in all controller methods.

Related

Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for init

I'm maintaining a PHP Zend application. I'm attempting to add functionality to it.
I'm trying to call a Controller through a phtml file. I'm thinking I'm approaching this the wrong way, but I'm not sure what the correct way is.
I've modified three files and added another. I've added code to FileController.php.
public function getModifiedBy($filename) {
$groupFiles =$this->getServiceLocator()->get('qatools\Model \GroupFilesTable');
$modified = $groupFiles->fetch($filename);
return $modified;
}
I've also added code to job-wizard.phtml.
<?php
use qatools\Controller\FileController;
$fileControl = new FileController;
$fileControl->init();
$modified =$fileControl->getModifiedBy("addresscleaningservice.xlsx");
?>
The new file is 'GroupFileTable.php' which extend AbstractModelTable and queries a MySQL database. I added the following lines to module.config.php.
'qatools\Model\GroupFilesTable' => function($sm) {
return defModelTable($sm, 'GroupFilesTable');
},
'GroupFilesTableGateway' => function($sm) {
return defModelTableGateway($sm, 'group_files', 'GroupFiles');
},
The code is failing on $fileControl->init() in job-wizard.phtml. I tried taking out that line, but then the code failing on the getServiceLocator call.
#0 /mnt/c/git-repos/qatools/vendor/zendframework/zendframework/library /Zend/ServiceManager/AbstractPluginManager.php(103): Zend\ServiceManager\ServiceManager->get('init', true)
#1 /mnt/c/git-repos/qatools/vendor/zendframework/zendframework/library/Zend/Mvc/Controller/PluginManager.php(82): Zend\ServiceManager\AbstractPluginManager->get('init', NULL, true)
You didn't specify what version of Zend Framework are you using, anyway whatever version is, you should not create an istance of a controller within a view script.
In ZF, if you need to call a view functionality, a better approach is to create a view helper, the logic between ZF1 and ZF2/3 is the same, only the implementation changes, I'll leave to you google searches to see examples, your final view script code should be something like the follow
<?php echo $this->getModifiedBy("addresscleaningservice.xlsx"); ?>
The viewHelper will contain the logic. If at some point you'll need to reuse this logic within a controller or in whatever other place, my advice is to create a component with the logic, then create a service that returns the instance of this component and eventually create an action helper ( $this->myComponent in any controller ) and a view helper ( $this->myComponent in any view ) and inject the same component instance.

How to run no action and a single variable in the Silverstripe url_handlers?

I'm working on a project which has a special page type which checks a single 250 alphanumeric string.
Ideally I'd like the URL structure to work like this:
http://www.example.com/check/I1gdTVUsnezY9SDI8V0GS2mg7Y0IdG6MqjCZ8t1yejRdi0pKzyr7G28iF0fyxOW9Le9vg3op7NnuCE0unT7d09aN00Trn7xPYAjLRhqQ9k5aRlsThsTk0HaS966MCDb4aC23RW4Cl273e9YiWKFNm2STI75X1jnlZ684M7ejDpmWg1YfM32OpwX066bF5VTp5v0F5I42T2SWh8QhMc9GW9I2ZbuP7ykh710UHnLwQyA3BO7KitZWcCU0u9
However using allowed_actions and url_handlers the standard way I can only get it to work if I preface the alphanumeric string with "uid" - http://www.example.com/check/uid/string-goes-here
class CheckPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'uid'
);
private static $url_handlers = array(
'uid/$uID' => 'uid'
);
Is it possible to have url_handlers work with just a variable and no action on a custom page?
You can define a route without a action in your _config.php:
Director::addRules(100, array(
'check/$UID' => 'CheckPage_Controller'
));
and in your CheckPage_Controller you can catch the request in the index function:
public function index() {
var_dump($this->request->allParams());die;
}
But you should be aware that the add route catches all requests to /check/whatever. So you need to define a different url for other stuff.
Assuming you have a pagetype "CheckPage" with $URLSegment "check" and some Dataobjects in a has_many relation you can use Nightjars extension for using this urlslug method, which is really elegant:
https://github.com/NightJar/silverstripe-slug/
ATM it's no ready to install module but an extension to the controller you can configure.
If you need any further help please provide some more informations about your code structure.

How can I use Model functions correctly according to the MVC pattern in CakePHP?

Can I do this in a Controller:
$this->User->read(null, $id);
$this->User->find('list');
Is it correct?
Am I using MVC correctly?
Can these easy functions be used in a Controller? Or, do I need to create these functions in the Model? Like Model->getUser(), and have that function use Model->read().
I know that functions it's called by Model, but, when I want pass some parameters, and function makes big, for example:
$this->User->find('all', array(
'conditions' => array(
'User.active' => true,
'User.group_id' => 3,
'User.age >=' => 18
)
));
Can I call this function in Controller, or need create a custom function in Model, to call it? Like... $this->User->findSomeCustomFunction($param1, $param2, $param3)?
TLDR:
It's "ok" to call a find() from your Controller, however best practice is to put any/all find()s in your models.
If you make a habit of putting all your find()s in your models, it will make it much easier to maintain your code in the long run.
Explanation/example:
In this case, as an example, you could start with a seemingly simple function:
//User model
public function getUsers() {
return $this->find('list');
}
But later, maybe you need something more along the lines of:
//User model
public function getUsers($opts = array()) {
$defaults = array(
'findType' => 'all',
'activeOnly' => true,
);
$params = array_merge($defaults, $opts);
$qOpts = array('conditions' => array());
//active only
if(!empty($params['activeOnly'])) $conditions[$this->alias.'.active'] = 1;
return $this->find($params['findType'], $qOpts);
}
(Pardon if there are many ways to make that code better - it was just off the top of my head - It gives you the idea.)
Keeping all your find()s in the Model also keeps you from having to search through each Controller every time you want to write a find() to determine if you've used a similar find() anywhere else. If you're programming as a team, that can be a nightmare, and you're almost guaranteed to be duplicating code.
It is perfectly fine to call Model->find() from a Controller. However, you will also want follow the DRY (Don't Repeat Yourself) principles. That basically means "Don't copy-paste code everywhere."
So, if you find that you need to make this exact Model->find() call from many Controller actions, it is considered good practice to abstract it into a function call against the Model. So yes, your Controllers would then call $this->User->findSomeCustomFunction().

createAbsoluteUrl generates a path-like URL, how to avoid it?

I create a URL like this:
$app->createAbsoluteUrl('/', array(
'param1' => 'val1',
'param2' => 'var2',
);
The generated URL is:
http://mysite.com/param1/var1/param2/var2
But I expect a url like this:
http://mysite.com/?param1=var1&param2=var2
In function manual it says:
$params array additional GET parameters (name=>value). Both the name and value will be URL-encoded.
But it doesn't seem to work like that. How I can generate the expected URL? Thanks.
You need to specify that the urlManager application component should use the "get" format for the URLs it generates; the default is to use the "path" format. The Yii guide explains how to do it inside your application configuration:
array(
......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'get',
),
),
);
Update: So your urlFormat is "path" and that's by design... what about alternatives?
If you don't mind extending CWebApplication and using your own derived class in its place then you have several options such as:
Define your own createUrlEx method based on the original createUrl. It could look like this:
public function createUrlEx($format,$route,$params=array(),$ampersand='&')
{
$oldFormat = $this->getUrlManager()->getUrlFormat();
$this->getUrlManager()->setUrlFormat($format);
$url = $this->getUrlManager()->createUrl($route,$params,$ampersand);
$this->getUrlManager()->setUrlFormat($oldFormat);
return $url;
}
Override registerCoreComponents so that you can have a second url manager:
protected function registerCoreComponents()
{
parent::registerCoreComponents();
$components=array(
'specialUrlManager'=>array(
'class'=>'CUrlManager',
'urlFormat'=>'get',
),
);
$this->setComponents($components);
}
You can now call Yii::app()->specialUrlManager->createUrl(...) anytime.
You can also approach the problem in other ways:
Extend CUrlManager and expose a method that allows you to select the flavor of url to create on the spot.
If you only need "get" urls in one or two places, you can always create a new CUrlManager object, configure it on the spot, call createUrl and then discard it. You could also hide this ugliness behind a free function. Essentially this (admittedly not recommended) approach is a low-tech version of the first workaround given that has the advantage that you don't need to extend CWebApplication.
You should be able to use something like Yii::app()->urlManager->createPathInfo This will generate the query string as ...&var=val&... using a custom & and = if you like. You could use this to create a query string version of a URL on demand with:
$url = $this->createAbsoluteUrl('/').'index.php?'.Yii::app()->urlManager->createPathInfo($arrayOfStuff);
Or you might even be able to do:
Yii::app()->urlManager->urlFormat = 'get';
$this->createAbsoluteUrl('My/Path');
Yii::app()->urlManager->urlFormat = 'path';
Although I haven't and don't want to test the second method.
URL route should be in the format of 'ControllerID/ActionID'. manual
Your manual link is for CController and isn't for CApplication.

how to use Lithium PHP framework with enumerated list of collections and changing collection in model

I'm looking to use Lithium framework to build my application config interface as I like its minimal approach and the document-store (i.e. Mongodb) centric model.
However, (and I know its not quite released yet), there is little-to-no information, tutorials or examples out there to move you on from the simple blog tutorial.
What I am trying to do now is build an app that will show me the collections I have in Mongodb, and then let me work with which ever collection I choose. I can't seem to figure out:
a) how would I build a model that enumerates the collections - preferably according to my internal naming scheme,
b) how do I break the convention model so I can specify the name of the collection to use?
I think there are two things i'm struggling with to answer these two questions - perhaps a fundamental misunderstanding of how to move a model in MVC beyond the simple collection-model-controller-view examples, and secondly, the actual process of telling the mongo datasource what collection to use.
any pointers or examples, gratefully received.
Chris
update::
So I figured out how to set the collection - for reference you can set source in the $_meta array like this:
protected $_meta = array(
'source' => '<<collectionName>>'
);
still no idea how to use a Model that will list me all the collections I have in my DB though. Any ideas how to do that from a philosophical and also technological manner?
further update::
so I have got a bit further thanks to the comments below. At least I might now be able to re-phrase the question a bit. I can define my model something like this:
<?php
namespace app\models;
use lithium\data\Model;
class Posts extends \lithium\data\Model{
protected $_meta = array('source' => false);
public function testcolls(){
return (self::connection()->sources());
}
}
?>
then in my view I can use:
<?php foreach ($post->testcolls() as $coll): ?>
<h2><?=$coll ?></h2>
<?php endforeach; ?>
that works - however, what I really want to do is not create a 'testcolls' method in my Model but as Medhi suggested below, I need to override the find method. I can't seem to figure out how to do that and what it would need to return. The docs are not too clear on this.
final update
based on the comment below and a bit of experimentation, I came up with the following that works for being able to call find with a collection as a parameter.
model:
class Dataqueues extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
if (isset($options['collection'])){
self::meta('source', $options['collection']);
}
return parent::find('all',$options);
}
}
controller:
class DataqueuesController extends \lithium\action\Controller {
public function index() {
$dataqueues = Dataqueues::find('all',array('limit'=>20,'collection'=>'W501'));
return compact('dataqueues');
}
}
getting a model that returns a list of collections was also pretty simple in the end:
class Collections extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
return self::connection()->sources();
}
}
note that the controller won't support options or filters.
Nothing holds you from having a Collections Model, where you set $_meta['source'] = false to prevent Lithium from looking for a Collection in your database named collections.
In this model, you can call YourModel::connection()->sources() to list all your Mongo Collections.
Docs for sources(): http://li3.me/docs/lithium/data/source/MongoDb::sources(). Basically it calls listCollections() on a MongoDB instance http://php.net/manual/en/mongodb.listcollections.php
You can override your Model::find() method to return the list of collections, instead the list of documents, or pass the collection as a param Collections::find('all', array('conditions' => ..., 'collection' => 'foo'))... or wathever you want :-)
Lithium is designed to don't force that much on you !
First of all, Lithium follows the convention over configuration approach.
What this means:
Configuration: 'source' => '<< collectionName >>'
Convention: Name your model and your collection the same thing, the framework handles the rest.
IE: A "People" collection will have a "People" model
Second, connect to your database:
Configure your connections.php file in app\bootstrap\connections.php. I know I said convention over configuration, but you still need to let the framework know where the database is and what the login info is. For details look at the http://li3.me/docs/manual/quickstart. Here is the relevant code:
// MongoDB Connection
Connections::add('default', array('type' => 'MongoDb', 'database' => 'blog', 'host' => 'localhost'));
Third, get data
Create a model, matching your collection name, and then in your controller, add this line:
$model = Models::all();
where model is the singular name for what you are storing in your collection, and Models is the name of your model. That is it.
If you put a break point after this line, you will see your Models collection. For more information, see http://li3.me/docs/manual/working-with-data/using-models.wiki
Finally, to pass it to your view, simply put this line of code at the end of your controller:
return compact('model', 'model2', 'model3');
where model would be what you just pulled in the third step. The models 2 and 3 that I tacked on is how you would pass any other collections you pulled.
In your view, you would just reference $model to and assume that the relevant fields are there. You don't have to worry about putting getters or setters or anything else like that.
For example: if you want to show the data in $model:
foreach ($model as $aModel) {
echo $aModel;
}
See Accessing View Variables in: http://li3.me/docs/manual/handling-http-requests/views.wiki
Hope this helps.

Categories