I need to share data between a component and helper. I'm converting my self-made payment service formdata generator to a CakePHP plugin and I'd like to be able to fill in the payment data from the controller(using a component) and use a helper to print out the data.
Everything I've tried so far have felt a little too hacky, so let me ask you: Is there any elegant way to pass data from a component to a helper?
edit:
I solved this particular situation by adding the original formadata class instance to ClassRegistry during the component initialization. This way the helper too can access the instance using ClassRegistry.
However, this only works for objects, so the question remains open.
Having a similar problem, I found this solution to work best for me.
You could use the helper's __construct method in pair with $controller->helpers array.
Since the Helper::_construct() is called after the Component::beforeRender, you can modify the $controller->helpers['YourHelperName'] array to pass the data to your helper.
Component code:
<?php
public function beforeRender($controller){
$controller->helpers['YourHelperName']['data'] = array('A'=>1, 'B'=>2);
}
?>
Helper code:
<?php
function __construct($View, $settings){
debug($settings);
/* outputs:
array(
'data' => array(
'A' => (int) 1,
'B' => (int) 2
)
)
*/
}
?>
I am using CakePHP 2.0, so this solution should be tested for earlier versions.
Is there any elegant way to pass data from a component to a helper?
Yes, the same way you pass any data to the helper. In your view.
Inside your component I would do something like the following. The beforeRender() action is a CakePHP component callback.
public function beforeRender(Controller $controller) {
$yourVars = 'some data';
$goHere = 'other stuff';
$controller->set(compact('yourVars', 'goHere'));
}
Then in your view you can pass the data off to your helpers just like normal.
// view or layout *.ctp file
$this->YourHelper->yourMethod($yourVars);
$this->YourHelper->otherMethod($goHere);
In addition to what #Vanja, you can also do this just prior to instantiating a new view in your controller:
// In your controller method
// must be set prior to instantiating view
$this->helpers['YourHelperName']['paramsOrAnyName'] = ['var' => $passed_var];
$_newView = new View($this);
$return_result = $_newView->render($element_to_view, $layout);
Related
I have an API like example
I have used cakephp HTTP client to get data, below my attempted code
public index()
{
$http = new Client();
$response = $http->get('https://restcountries.eu/rest/v2/all');
// $json = $response->getJson(); //also tried usgin json
$countries = $this->paginate($response);
$this->set(compact('countries '));
}
I am trying to apply pagination with this country data then fetch it in view with pagination.
After tried above code , I have gotten below error
Argument 1 passed to Cake\Datasource\Paginator::extractData() must be an instance of Cake\Datasource\RepositoryInterface, instance of Cake\Http\Client\Response given, called in \myapp\vendor\cakephp\cakephp\src\Datasource\Paginator.php on line 176
How can I get my desire result ?
You have probably need to implement a class who extend RepositoryInterface.
class JsonSource implements Cake\Datasource\RepositoryInterface
{ ... }
public index() {
$http = new Client();
$response = $http->get('https://restcountries.eu/rest/v2/all');
$src = new JsonSource();
$src->fromResponse($response);
$countries = $this->paginate($src);
$this->set(compact('countries ')); }
Is a bit tedious, because you need to define Json like a datasource.
The default pagination only supports querying tables (repositories), or operating on pre-built query instances.
To extend on #Zeppi's answer. You basically have three somewhat straightforward options here:
Create custom query/repository implementations as hinted by #Zeppi.
This can indeed be quite a lot of work though, so you might want to look into alternatively implementing it with the help of plugins, for example muffin/webservice, which does most of the hard work of implementing the required interfaces.
Or create a custom paginator that actually accepts and works on array data.
Or use what is widely know as a "datatable", that is a JavaScript based table in the frontend that paginates the data, for example jQuery DataTables.
I'm building a dynamic view (Page) that consists of multiple elements (widgets) called via $this->element('messages_unread'). Some of these elements need data that is not related to the Page model.
In real life words: my users will be able to construct their own Page by choosing from a multitude of elements ("top 5 posts", "10 unread messages", etc...)
I get the data by calling $this->requestAction(array('controller'=>'events','action'=>'archive') from within the element, the url-variables differ per element .
I'm aware of the fact that requestAction() is expensive and I plan on limiting the costs by proper caching.
The actual question:
My problem is Pagination. When I'm in the Page view and call requestAction('/events/archive') the PaginatorHelper in the Page view will be unaware of the Event model and its paginator variables and $this->Paginator->next() etc... will not work.
How can I implement proper Pagination? I've tried to set the model by calling $this->Paginator->options(array('model'=>'Event')) but that doesn't work.
Do I maybe need to return custom defined Pagination variables in the requestAction and thus construct my own?
Or is there another approach that maybe even avoids requestAction()? And keep in mind here that the requested data is unrelated to the Page.
Kind regards,
Bart
[Edit] My temporary solution but still open for comments/solutions:
In the requestedAction Event/archive, return paginator variables along with the data like this:
return array('data'=>$this->paginate(), 'paging' => $this->params['paging']);
I've tinkered a bit more and the following works for me, and the PaginationHelper works:
In the element:
// requestAction returns an array('data'=>... , 'paging'=>...)
$data = $this->requestAction(array('controller'=>'events','action'=>'archive'));
// if the 'paging' variable is populated, merge it with the already present paging variable in $this->params. This will make sure the PaginatorHelper works
if(!isset($this->params['paging'])) $this->params['paging'] = array();
$this->params['paging'] = array_merge( $this->params['paging'] , $data['paging'] );
foreach($data['events'] as $event) {
// loop through data...
}
In the Controller:
public function archive() {
$this->paginate = array(
'limit' => 10
);
if ($this->params['requested'])
return array('events'=>$this->paginate('Event'), 'paging' => $this->params['paging']);
$this->set('events', $this->paginate('Event') );
}
I searched a lot but I couldn't find on How to use the find('all') in Views as used in Rails, but here I'm getting the error "Undefined property: View::$Menu [APP\Lib\Cake\View\View.php, line 804]"
'Menu' is the model which I'm using to fetch data from the menus table.
I'm using the below code in views:
$this->set('test',$this->Menu->find('all'));
print_r($test);
Inside your Menu model create a method, something like getMenu(). In this method do your find() and get the results you want. Modify the results as you need and like to within the getMenu() method and return the data.
If you need that menu on every page in AppController::beforeFilter() or beforeRender() simply do
$this->set('menu', ClassRegistry::init('Menu')->getMenu());
If you do not need it everywhere you might go better with using requestAction getting the data using this method from the Menus controller that will call getMenu() from the model and return the data. Setting it where you need it would be still better, if you use requestAction you also want to cache it very likely.
TRY TO NOT RETRIEVE DATA WITHIN VIEW FILE. VIOLATION OF MVC RULE
try this in view file:
$menu = ClassRegistry::init('Menu');
pr($menu->find('all'));
In AppHelper ,
Make a below function
function getMenu()
{
App::import('Model', 'Menu');
$this->Menu= &new Menu();
$test = array();
$test = $this->Menu->find('all');
return $test;
}
Use above function in view like :
<?php
$menu = $html->getMenu();
print_r($menu);
?>
Cakephp not allow this .
First create the reference(object) of your model using ClassRegistry::init('Model');
And then call find function from using object
$obj = ClassRegistry::init('Menu');
$test = $obj->find('all');
echo ""; print_r($test); `
This will work.
Okay, this will require some setup:
I'm working on a method of using nice post title "slugs" in the URL's of my cakePHP powered blog.
For example: /blog/post-title-here instead of /blog/view_post/123.
Since I'm obviously not going to write a new method for every post, I'm trying to be slick and use CakePHP callbacks to emulate the behavior of PHP 5's __call() magic method. For those who do not know, CakePHP's dispatcher checks to see if a method exists and throws a cakePHP error before __call() can be invoked in the controller.
What I've done so far:
In the interest of full disclosure ('cause I have no Idea why I'm having a problem) I've got two routes:
Router::connect('/blog/:action/*', array('controller' => 'blog_posts'));
Router::connect('/blog/*', array('controller' => 'blog_posts'));
These set up an alias for the BlogPostsController so that my url doesn't look like /blog_posts/action
Then in the BlogPostsController:
public function beforeFilter() {
parent::beforeFilter();
if (!in_array($this->params['action'], $this->methods)) {
$this->setAction('single_post', $this->params['action']);
}
}
public function single_post($slug = NULL) {
$post = $this->BlogPost->get_post_by_slug($slug);
$this->set('post', $post);
//$this->render('single_post');
}
The beforeFilter catches actions that do not exist and passes them to my single_post method. single_post grabs the data from the model, and sets a variable $post for the view.
There's also an index method that displays the 10 most recent posts.
Here's the confounding part:
You'll notice that there is a $this->render method that is commented-out above.
When I do not call $this->render('single_post'), the view renders once, but the $post variable is not set.
When I do call $this->render('single_post'), The view renders with the $post variable set, and then renders again with it not set. So in effect I get two full layouts, one after the other, in the same document. One with the content, and one without.
I've tried using a method named single_post and a method named __single_post and both have the same problem. I would prefer the end result to be a method named __single_post so that it cannot be accessed directly with the url /blog/single_post.
Also
I've not yet coded error handling for when the post does not exist (so that when people type random things in the url they don't get the single_post view). I plan on doing that after I figure out this problem.
This doesn't explicitly answer your question, but I'd just forego the whole complexity by solving the problem using only routes:
// Whitelist other public actions in BlogPostsController first,
// so they're not caught by the catch-all slug rule.
// This whitelists BlogPostsController::other() and ::actions(), so
// the URLs /blog/other/foo and /blog/actions/bar still work.
Router::connect('/blog/:action/*',
array('controller' => 'blog_posts'),
array('action' => 'other|actions'));
// Connect all URLs not matching the above, like /blog/my-frist-post,
// to BlogPostsController::single_post($slug). Optionally use RegEx to
// filter slug format.
Router::connect('/blog/:slug',
array('controller' => 'blog_posts', 'action' => 'single_post'),
array('pass' => array('slug') /*, 'slug' => 'regex for slug' */));
Note that the above routes depend on a bug fix only recently, as of the time of this writing, incorporated into Cake (see http://cakephp.lighthouseapp.com/projects/42648/tickets/1197-routing-error-when-using-regex-on-action). See the edit history of this post for a more compatible solution.
As for the single_post method being accessible directly: I won't. Since the /blog/:slug route catches all URLs that start with /blog/, it'll catch /blog/single_post and invoke BlogPostsController::single_post('single_post'). You will then try to find a post with the slug "single_post", which probably won't exist. In that case, you can throw a 404 error:
function single_post($slug) {
$post = $this->BlogPost->get_post_by_slug($slug);
if (!$post) {
$this->cakeError('error404');
}
// business as usual here
}
Error handling: done.
Ok i have this controller:
class ExampleController extends AppController {
var $name = 'Example';
public function test_me () {
$this->Example->Create();
$this->Example->set( 'variable_from_db_1' => 'random_value_1',
'variable_from_db_2' => 'random_value_2' );
//here, how can i access to variable_from_db_1 and 2 in $this->Example?
//???? i've tried $this->data and $this->Example->data but nothing to do
}
}
Do you have some hints for me?
you can explore the data with:
debug( $this->Example );
the data is it's own array:
$this->Example->data['variable_from_db_1'];
I don't think you can do that.
You can assign your model data to a $this->data array like this:
$this->data['variable_from_db_1'] = $value;
$this->set('variable_from_db_1', $value);
So know you can access $this->data within the controller.
I think if you want to save data to your actual Model, you might have to implement the getter / setter method in your model...
In the related view you can access it like this:
echo $variable_from_db_1.'<br />';
echo $variable_from_db_2.'<br />';
In the controller call
debug($this->data);
I think you cannot do this in controller, normally set calls are moved to the end area of actions and all the operations on the such variables must be performed before 'set'ing it for later access in views.