Difference in accessing variables in views - arrays

I've two controllers one is "Upload" which deals with images uploads and other is "Page" whid deals with the creation of pages of CMS now if in my "Upload" controller I load both the models i.e 'image_m' which deals with image upload and "page_m" which deals with the pages creation I've highlighted the relevant code my problem is if I access the variables in the view
$this->data['images'] = $this->image_m->get(); sent by this I can access in foreach loop as "$images->image_title, $images->image_path" etc
But the variable sent by this line ***$this->data['get_with_images'] = $this->page_m->get_no_parents();*** as $get_with_images->page_name, $get_with_images->page_id etc produces given error
A PHP Error was encountered
Severity: Notice
Message: Trying to get property of non-object
Filename: upload/index.php
Line Number: 20
what is the difference between these two access levels one for $image & other for $get_with_images because I can only access its values as $get_with_images
class Upload extends Admin_Controller {
public function __construct() {
parent::__construct();
***$this->load->model('image_m');
$this->load->model('page_m');***
}
public function index($id = NULL) {
//var_dump($this->data['images'] = $this->image_m->get_with_images());
//$this->data['images'] = $this->image_m->get_with_images();
***$this->data['images'] = $this->image_m->get();***
$this->data['subview'] = 'admin/upload/index';
if ($id) {
$this->data['image'] = $this->image_m->get($id);
count($this->data['image']) || $this->data['errors'][] = 'Page Could not be found';
}
$id == NULL || $this->data['image'] = $this->image_m->get($id);
/*this calls the page_m model function to load all the pages from pages table*/
***$this->data['get_with_images'] = $this->page_m->get_no_parents();***

You are not posting all your code so its hard to tell but is it because you used $this-> in the controller, but you haven't done the same thing in the view?
In this case i would recommend not using $this-> because its not necessary. Also its much better to check for errors etc when you call the model so do something like
if ( ! $data['images'] = $this->image_m->get($id) ) {
// Failure -- show an appropriate view for not getting any images
// am showing $data in case you have other values that are getting passed
$this->load->view( 'sadview', $data ); }
else {
// Success -- show a view to display images
$this->load->view( 'awesomeview', $data ); }
so we are saying if nothing came back - the ! is a negative - then show the failure view. Else $data['images'] came back, and it will be passed to the view. note i have not had to use $this-> for anything and it won't be needed in the view.
Would also suggest using separate methods - have one method to show all images and a separate method like returnimage($id) to show an image based on a specific validated $id.
====== Edit
You can access as many models as you want and pass that data to the View. You have a different issue - the problem is that you are waiting until the View to find out - and then it makes it more difficult to figure out what is wrong.
Look at this page and make sure you understand the differences between query results
http://ellislab.com/codeigniter/user-guide/database/results.html
When you have problems like this the first thing to do is make a simple view, and echo out directly from the model method that is giving you problems. Its probably something very simple but you are having to look through so much code that its difficult to discover.
The next thing is that for every method you write, you need to ask yourself 'what if it doesn't return anything?' and then deal with those conditions as part of your code. Always validate any input coming in to your methods (even links) and always have fallbacks for any method connecting to a database.

On your view do a var_dump($get_with_images) The error being given is that you are trying to use/access $get_with_images as an object but it is not an object.
or better yet on your controller do a
echo '<pre>';
var_dump($this->page_m->get_no_parents());
exit();
maybe your model is not returning anything or is returning something but the data is not an object , maybe an array of object that you still need to loop through in some cases.

Related

Defining relationships and processing data within them

I have got myself a bit confused with relationships, I am not sure if I am splitting things up too much? I am dealing with a reporting system where there can be different types of reports. So, I have my standard reports table and model, and within the model, I define that a report can have one report type.
public function reportType() {
return $this->hasOne('App\ReportType');
}
Now a reportType can be one of three different Types, lets call them A, B and C. Each reportType collects different data. As such, within the ReportType model, I define a has one relationship with the type it has
public function reportTypeA() {
return $this->hasOne('App\ReportTypeA');
}
So the above states that a reportType can have one reportTypeA. I also have in this Model that it can have one reportTypeB and C. Now within reportTypeA model, I state that reportTypeA can have one set of reportTypeAData
public function reportTypeAData() {
return $this->hasOne('App\ReportTypeAData');
}
I have the inverse relationships in all Models. So essentially, I have
Report->ReportType->ReportTypeA->ReportTypeAData
ReportTypeB->ReportTypeBData
ReportTypeBData2
ReportTypeC->ReportTypeCData
Reason I have the data models is because some reports have more than one set of data. So the above shows that Report B has 2 sets of data, each with its own structure.
So the above works, but it seems very "waterfall" approach to me. I will clean this up at some point to hopefully make it more structured.
This is where I am confused though, with the above approach, how can I get the data for a Report? So in my controller, I have something like
$report = Report::where('user_id', Auth::user()->id)->first();
This will get me the first report for the logged in User. I can then get the report data doing something like this
$reportData = $report->reportType->reportTypeA->reportTypeAData->all()->toArray();
Which seems proper overkill having to go through all relationships. My main problem is this, I want to chunk the data back to the frontend, so I will have something like this
DB::table("report_type_a_data")->chunk(100, function ($data) use ($handle) {
foreach ($data as $row) {
// Add a new row with data
fputcsv($handle, [
$row->id,
$row->name
]);
}
});
Now obviously that will loop all data, where I only want the data for the report I am dealing with. Additionally, when I try this, I get an error
You must specify an orderBy clause when using this function
Why am I getting this? Any help with organising things better and how I can process a specific reports data is highly appreciated.
Thanks
You need orderBy() when you use chunk().
For example.
DB::table('users')->orderBy('id')->chunk(100, function ($users) {
foreach ($users as $user) {
//
}
});
or you can use chunkById().
DB::table('users')->where('active', false)
->chunkById(100, function ($users) {
foreach ($users as $user) {
//
}
});
Please refer to the document

Pagination in requestAction

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') );
}

validateAssociated overwriting associated model's errors, possible bug?

I'm running with cakephp version 2.0.2 and was scratching my head as to why a form submission that submits data to an association of models was not revealing error messages for the associations.
I've been digging into the Model class to diagnose further. I found that if the primary model for the form had its own validation errors, then no validation errors for any associations would ever be revealed in the returned:
$this->validationErrors
But I think I found the smoking gun. In the Model.php's validateAssociated method, you'll see this:
$this->validationErrors = $validationErrors;
if (isset($validationErrors[$this->alias])) {
$this->validationErrors = $validationErrors[$this->alias];
}
The first line sets $this->validationErrors to contain all built up errors across all associations. But if $validationErrors contains errors for the key of $this->alias which is the primary model name, then as you can see, $this->validationErrors gets overwritten to just those errors.
So this begs the question.... why? I'm so certain this is a bug I want to modify my Model.php and I think it'll work. But I wanted to get this in front of others in case I'm doing something really stupid here.
I had the same issue today. Its like that for BC. I know, it sucks. It should be a bug. The way I work around it is by re-formatting validation errors.
// AppModel.php
public function formatValidationErrors($models) {
foreach($models as $model => $assoc) {
if (is_numeric($model)) {
$model = $assoc;
$assoc = null;
}
$this->validationErrors[$model] = $this->{$model}->validationErrors;
if ($assoc) {
$this->{$model}->formatValidationErrors($assoc);
}
}
}
I call that if validation fails, and pass an array like you would do to contain. You can use that if you don't want to modify the core.

Need to make full names in cakePHP

If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.

How do I send arrays between views in CakePHP

I am not sure if I formulated the question right, but still ...
I have a view that shows a flash embed and this flash take as parameter a /controller/action URL that generates a XML. I nee to send, from this view, an array to the XML generator action. How is the best way ? Is there some helper->set() method like or I have to create an specific URL to send this array to that action ?
Here goes my structure:
my_controller.php
function player() {}
player.ctp
<div id="myDiv">Here it Goes</div>
<script type="text/javascript">
var so = new SWFObject('player.swf','test','50','50','8');
so.addVariable('file','/xml/generate'); // need to pass an array here
so.write('myDiv');
</script>
xml_controller.php
public function generate() {
// I need to read an array here
}
generate.ctp
echo "<xml><data>" . $array['contents'] . "</data>";
If the array is small enough, serialize then urlencode it and add it as a paramter to the url to your generate action:
player.ctp
so.addVariable('file','/xml/generate/<?php echo urlencode(serialize($array)); ?>');
then read it back:
public function generate($array) {
$array = unserialize($array);
}
Save the array in the session then in the next request to the XML generator action, read it back from the session.
my_controller.php
function player() {
$this->Session->write('key', $array);
}
xml_controller.php
public function generate() {
$array = $this->Session->read('key');
}
However, I have heard of some problems where flash sometimes doesn't send session cookies, in which case, append the session id to the url of the action:
so.addVariable('file','/xml/generate/<?php echo $session->id(); ?>');
and to get the session back:
public function generate($sessionId) {
CakeSession::id($sessionId);
$array = $this->Session->read('key');
}
First of all you cannot send data from one view to another in the manner you are speaking. Each of those calls would be a separate request and this means that it goes out of the framework and then in again. This means that the framework will be built and tear down between calls, making impossible to pass the data between views.
Now in regards to the array that has to be sent to your action, I'm utterly confused. I don't think you are looking at the problem the right way. If that action needs an array of data and then produce XML so the Flash Object can get it, then it makes even less sense. Are you sure that the Flash Object isn't the one responsible to sending that array of data to the Param you mentioned?
Well, even if all you are saying has to be done quite like that, I'll suggest you drop that array on the file system and then pick it up when the action is called by the Flash.
Or another suggestion would be to use AJAX to send that array to the action.
Both suggestions imply my utter "cluelessness" on your predicate.
I still have to ask, isn't the Flash Object gonna do something in all this?
You can send an array with data from a view to a controller in CakePHP like this.
To the link you can pass arguments:
www.site.com/model/action/param1:foo/param2:test
You can then retrieve them in the controller action in the following way:
$yourarray = $this->params['named'];
Of course the array shouldn't be too large then.

Resources