I'm working at understanding best practices for MVC (using CakePHP) and am trying to understand if a certain task should be happening in the controller or the view.
Here's the scenario:
I have a table of users. Each user has many events associated with them.
In my controller I'm loading content about a user into an array like so:
$this->set('user', $this->User->read());
That results in a user array I can loop through on my view page:
Array
(
[User] => Array
(
[id] => 1
[name] => Jane Doe
[created] => 2011-03-29 15:50:25
[modified] => 1301428225
)
[Event] => Array
(
[0] => Array
(
[id] => 4
[user_id] => 1
[title] => Birthday
[created] => 2011-04-07 17:28:53
[modified] => 2011-04-07 17:28:53
[occured] => 1301889600
)
[1] => Array
(
[id] => 3
[user_id] => 1
[title] => Anniversary
[created] => 2011-04-07 17:21:27
[modified] => 2011-04-07 17:21:27
[occured] => 1301976000
)
[2] => Array
(
[id] => 2
[user_id] => 1
[title] => Graduation
[created] => 2011-04-07 17:20:41
[modified] => 2011-04-07 17:20:41
[occured] => 1301889600
)
)
Now, occured is a timestamp which I need to convert to a friendly date.
Should I:
A) Do this in the controller? If so, what's the best syntax to dig into the array and do that?
B) Do it in the view when it's called?
<?=date("d/m/Y,$thisEvent['occured']);?>
The latter seems cleaner with less code, but I don't know if it's logic I can / should be applying from the controller.
If you are going to loop through an array in the view anyway in order to display the events, I would just put the conversion in the view to save having to loop in the controller as well.
To me, the date formatting is a presentation thing - not a business logic thing.
Amy
The rule of thumb is this: Fat Model to Skinny View. The View should not handle logic. Period. The view should not handle alteration of data, it should only handle displaying it. The Model is where data manipulation should take place. But sometimes as coders we want to take the most easy way of doing things. Which isn't necessarily wrong, but isn't always the best way of doing thing.
My advice is one of the following (order of preference):
1- Alter the date format on the database so it comes out in the format you are looking for. Then there is no extra logic required for the formatting of the date.
2- Write the query as a join. (Which is what you should be doing in this case since recursive is on). Write the join so that the data comes back formatted as you expect. The problem is you are taking the shortcut by using $this->User-Read() which is causing you to have to write additional logic outside of the model to handle the formatting for the dates. The Model->read() has it's uses, this is not one of them.
3- If you are bent on using the Model->read() shortcut, you can build a date helper that you can reference from the view for any given date. For example, when you display the field in the view, you would call:
<?php echo $this->Date->format($thisEvent['occured']); ?>
Then the helper is where you would contain the code as follows:
function format($date) {
return date('d/m/Y', strtotime($date));
}
This way, if you ever decide you want to change the look of the dates, you only have to change the helper (1 location) instead of all of the views (multiple locations).
I would suggest that:
the Models return DateTime objects or similar. This way, it can be easy in the Controllers or the Views to make calculations/formatting on dates when necessary (good maintainability).
the Views, as said in another answer, should be kept as simple as possible (readability). With Smarty + working with specific DateTime objects, I can simply write {$someItem.someDate} in Views and the dates are formatted automatically (according to current user's language), by using a __toString() method of my specific DateTime class.
Related
I have two collections: "Instructions" and "Known". Basically I am taking a new set of "Instructions" and checking whether anything is different to what is "Known".
So, the quantity is not massive. I retrieve the info:
$Instructions = Instruction::all();
$Knowns = Known::all();
Now, I'm looking for the differences, and I've tried each of these three methods:
$IssuesFound = $Instructions->diff($Knowns);
$IssuesFound = $Instructions->diffKeys($Knowns);
$IssuesFound = $Instructions->diffAssoc($Knowns);
The thing is, an "Instruction" or "Known" is an item with 17 attributes, and anyone of those attributes can be different. I want to compare the attributes of an "Instruction" with the matching attribute of a "Known". (Both items have the same keys, bot items have a Reference attribute to act as a unique identifier.
What I'm finding is that theese methods give me the item that is different, but doesn't tell me which individual attributes are the mismatch.
foreach ($IssuesFound as $issue)
{
dd($issue);
}
So a method like $IssuesFound = $Instructions->diffKeys($Knowns); will come up with item xxx being different, but I can't see how to find out which attribute of the item it is that is different. Not unless I start nesting loops and iterating through all the attributes - which I'm trying to avoid.
How do I do it?
Thanks in advance. (Laravel 5.6)
Straight from laravel docs, diffAssoc will return what you are asking:
$collection = collect([
'color' => 'orange',
'type' => 'fruit',
'remain' => 6
]);
$diff = $collection->diffAssoc([
'color' => 'yellow',
'type' => 'fruit',
'remain' => 3,
'used' => 6
]);
$diff->all();
// ['color' => 'orange', 'remain' => 6]
You get the attribute from the FIRST collection that is different on the SECOND collection, therefore if you get 3 attributes when calling $diff->all() you will know WHICH attributes ARE DIFFERENT, so you could access them or do whatever you want to, if you post more specific results of what you are getting and what you are trying we can help, but I think you are just not thinking how to use these methods
Please could anyone explain to me a difference between [attributes:protected] array and [original:protected] array in laravel when using print_r to an array?
When Model reads data from table, arrays 'original' and 'attribute' contains same data. When you change the attribute value (ex $user->name='John'), the change is reflected only on the 'attributes' array but 'original' remains same. (hence the name).
When update() on a model is called, method checks what has changed comparing two arrays and construct query only for changed fields. Thus, in the case of $users->name change Laravel will not create this code:
UPDATE users set name = 'John', password = 'pass', email = 'email' where id = 1
but this:
UPDATE users set name = 'John' where id = 1
This may not be the only way Eloquent uses 'original' array. I found clockwork helpful when you need to see what's going on under the hood of Eloquent.
Our defined Type is something like this:
'title' => ... ,
'body' => ... ,
'links' => 'type'=>'object', 'properties'=> array
'link' => ... ,
'locations' => 'type'=>'object', 'properties'=> array
'label' => ... ,
'pin' => ...
)
The records/documents represent businesses that reside in one or more categories, and so the links array will contain all of the potential links to a business, i.e.
[0] => '/Businesses/Hotels/My-Business/',
[1] => '/Businesses/Resorts/My-Business/',
[2] => '/Businesses/Fractional-Ownership/My-Business/'
So when we run a query on the terms Business Resort this listing is included in the result set. At the moment though, we don't know which link would be most appropriate to display on the results page, so we just default to the first, in this case the one with ../Hotels/.. in the path.
Is it possible to order the links according to their own score/relevancy within the search so that the link order on the returned result would instead be:
[0] => '/Businesses/Resorts/My-Business/',
[1] => '/Businesses/Hotels/My-Business/',
[2] => '/Businesses/Fractional-Ownership/My-Business/'
The order of the links should not have any influence on the order of natural results from the overall query.
EDIT : The second use case which I've added above is, we also store locations for each business, and would like to order the location list for each resulting business by their proximity to a set of coordinates. We know how to order the entire result set by _geo_distance but need to know how to do it on a specific field, and like above, without affecting the overall result order.
Script based sorting will give you exactly what you want to sort on. But track_scores seems it will do what you're looking for, sorting doesn't use scores by default.
And for ordering geo distance sorting by a specific field you could do so client side, providing the specific field value of the selected field? I'm not sure this answers your second question.
Let me preface by saying I'm new to Magento as well as Data Collections in general (only recently begun working with OOP/frameworks).
I've followed the excellent tutorial here and I'm familiar with Alan Storm's overviews on the subject. My aim is to create a custom Magento report which, given a start/end date, will return the following totals:
Taxable Net (SUM subtotal for orders with tax)
Non-Taxable Net (SUM subtotal for orders without tax)
*Total Gross Sales (Grand total)
*Total Net Sales (Grand subtotal)
*Total Shipping
*Total Tax
*For these figures, I realize they are available in existing separate reports or can be manually calculated from them, however the purpose of this report is to give our store owner a single page to visit and file to export to send to his accountant for tax purposes.
I have the basic report structure already in place in Adminhtml including the date range, and I'm confident I can include additional filters if needed for order status/etc. Now I just need to pull the correct Data collection and figure out how to retrieve the relevant data.
My trouble is I can't make heads or tails of how the orders data is stored, what Joins are necessary (if any), how to manipulate the data once I have it, or how they interface with the Grid I've set up. The existing tutorials on the subject that I've found are all specifically dealing with product reports, as opposed to the aggregate sales data I need.
Many thanks in advance if anyone can point me in the right direction to a resource that can help me understand how to work with Magento sales data, or offer any other insight.
I have been working on something extremely similar and I used that tutorial as my base.
Expanding Orders Join Inner
Most of the order information you need is located in sales_flat_order with relates to $this->getTable('sales/order')
This actually already exists in her code but the array is empty so you need to populate it with the fields you want, here for example is mine:
->joinInner(
array('order' => $this->getTable('sales/order')),
implode(' AND ', $orderJoinCondition),
array(
'order_id' => 'order.entity_id',
'store_id' => 'order.store_id',
'currency_code' => 'order.order_currency_code',
'state' => 'order.state',
'status' => 'order.status',
'shipping_amount' => 'order.shipping_amount',
'shipping_tax_amount' => 'order.shipping_tax_amount',
'shipping_incl_tax' => 'base_shipping_incl_tax',
'subtotal' => 'order.subtotal',
'subtotal_incl_tax' => 'order.subtotal_incl_tax',
'total_item_count' => 'order.total_item_count',
'created_at' => 'order.created_at',
'updated_at' => 'order.updated_at'
))
To find the fields just desc sales_flat_order in mysql.
Adding additional Join Left
Ok so if you want information from other tables you need to add an ->joinLeft() for example I needed the shipment tracking number:
Create the Join condition:
$shipmentJoinCondition = array(
$orderTableAliasName . '.entity_id = shipment.order_id'
);
Perform the join left:
->joinLeft(
array('shipment' => $this->getTable('sales/shipment_track')),
implode(' AND ', $shipmentJoinCondition),
array(
'track_number' => 'shipment.track_number'
)
)
Sorry I couldn't go into more depth just dropping the snippet for you here.
Performing Calculations
To modify the data returned to the grid you have to change addItem(Varien_Object $item) in your model, basically whatever is returned from here get put in the grid, and well I am not 100% sure how it works and it seems a bit magical to me.
Ok first things first $item is an object, whatever you do to this object will stay with the object (sorry terrible explanation): Example, I wanted to return each order on a separate line and for each have (1/3, 2/3, 3/3), any changes I made would happen globally to the order object so they would all show (3/3). So keep this in mind, if funky stuff starts happening use PHP Clone.
$item_array = clone $item;
So now onto your logic, you can add any key you want to the array and it will be accessible in Grid.php
For example(bad since subtotal_incl_tax exists) :
$item_array['my_taxable_net_calc'] = $item['sub_total'] + $item['tax'];
Then at the end do:
$this->_items[] = $item_array;
return $this->_items;
You can also add more rows based on the existing by just adding more data to $this->_items[];
$this->_items[] = $item_array;
$this->_items[] = $item_array;
return $this->_items;
Would return same item on two lines.
Sorry I have started to lose the plot, if something doesn't make sense just ask, hope this helped.
Oh and to add to Block/Adminhtml/namespace/Grid.php
$this->addColumn('my_taxable_net_calc', array(
'header' => Mage::helper('report')->__('Taxable Net'),
'sortable' => false,
'filter' => false,
'index' => 'my_taxable_net_calc'
));
I have a mongodb collection that has documents like the ones below:
[
{
:event => {:type => 'comment_created'},
:item => {:id => 10},
:created_at => {:t => '11:19:03 +0100 2010', :d=> 'Fri, 19 Nov 2010'}
}
,
{
:event => {:type => 'vote_created'},
:item => {:id => 10},
:created_at => {:t => '11:19:03 +0100 2010', :d => 'Fri, 19 Nov 2010'}
}
]
What I need is to build a 'dashboard' aggregating latest activity (on current day) for each item. The result should be something like:
{
:item_id => 10,
:events => {
:vote_created => [.. ordered list with latest 3 vote_created events/documents],
:comment_created => [.. ordered list with latest 3 comment_created events/documents ],
}
}
The result would be used to construct a 'Facebook-style' syntax like: 'Mike, John and 3 others added comments on your item today.'
How can I aggregate this data using a group or a map-reduce function?
OK, there are two ways to do this:
Method #1: Map-Reduce
So first, you'll want to run a map-reduce, not a group.
Use Map-Reduce with the "out" variable which will generate a new collection. You'll then be able to run the summary queries against that new collection.
The reason you'll do this is that you're asking for an expensive query, so it's much more reasonable to access it in "not-quite" real-time.
Method #2: Double-writes
You can basically maintain two collections "details" (top one) and "summary" (bottom one). Whenever you do a write to the details, also perform an update to the summary.
MongoDB has several array methods ($push, $pull, $slice), that should make it possible to keep the "vote_created" array up-to-date.
Preferences
The method you select completely depends on the type of architecture you have and the user experience that you want. Personally, I would just use Method #2 and just keep appending to the "vote_created" array. I would put the 'Mike, John and 3 others...' syntax somewhere on the view, b/c it's really view logic not DB logic.
Yes method #2 takes more space, but it also gives you quick answers to the questions you ask alot. So you're going to have to sacrifice space to get that speed.
http://rickosborne.org/download/SQL-to-MongoDB.pdf