I am building a pretty simple online shop in SilverStripe. I am writing a function to remove an item from the cart (order in my case).
My setup:
My endpoint is returning JSON to the view for use in ajax.
public function remove() {
// Get existing order from SESSION
$sessionOrder = Session::get('order');
// Get the product id from POST
$productId = $_POST['product'];
// Remove the product from order object
unset($sessionOrder[$productId]);
// Set the order session value to the updated order
Session::set('order', $sessionOrder);
// Save the session (don't think this is needed, but thought I would try)
Session::save();
// Return object to view
return json_encode(Session::get('order'));
}
My issue:
When I post data to this route, the product gets removed but only temporarily, then next time remove is called, the previous item is back.
Example:
Order object:
{
product-1: {
name: 'Product One'
},
product-2: {
name: 'Product Two'
}
}
When I post to remove product-1 I get the following:
{
product-2: {
name: 'Product Two'
}
}
Which appears to have worked but then I try and remove product-2 with and get this:
{
product-1: {
name: 'Product One'
}
}
The SON OF A B is back! When I retrieve the entire cart, it still contains both.
How do I get the order to stick?
Your expectation is correct, and it should work with the code you wrote. However, the way the session data is managed doesn't work well with data being deleted, because it is not seen as a change of state. Only existing data being edited is seen as such. See Session::recursivelyApply() if you want to know more.
Only way I know is to (unfortunately) emphasized textmanipulate $_SESSION directly before you set the new value for 'order'
public function remove() {
// Get existing order from SESSION
$sessionOrder = Session::get('order');
// Get the product id from POST
$productId = $_POST['product'];
// Remove the product from order object
unset($sessionOrder[$productId]);
if (isset($_SESSION['order'])){
unset($_SESSION['order']);
}
// Set the order session value to the updated order
Session::set('order', $sessionOrder);
// Return object to view
return json_encode(Session::get('order'));
}
Related
If you are thinking this question is a beginner's question, maybe you are right. But really I was confused.
In my code, I want to know if saving a model is successful or not.
$model = Model::find(1);
$model->attr = $someVale;
$saveStatus = $model->save()
So, I think $saveStatus must show me if the saving is successful or not, But, now, the model is saved in the database while the $saveStatus value is NULL.
I am using Laravel 7;
save() will return a boolean, saved or not saved. So you can either do:
$model = new Model();
$model->attr = $value;
$saved = $model->save();
if(!$saved){
//Do something
}
Or directly save in the if:
if(!$model->save()){
//Do something
}
Please read those documentation from Laravel api section.
https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Model.html#method_getChanges
From here you can get many option to know current object was modified or not.
Also you can check this,
Laravel Eloquent update just if changes have been made
For Create object,
those option can helpful,
You can check the public attribute $exists on your model
if ($model->exists) {
// Model exists in the database
}
You can check for the models id (since that's only available after the record is saved and the newly created id is returned)
if(!$model->id){
App::abort(500, 'Some Error');
}
I'm not sure how they work, I was thinking that accessor fires when you access the attribute, however when I try to insert new record my accessor fires. I store in my database only image_name by removing URL and I use an accessor to include my URL route when I retrieve my image name. I found out that my accessor fires on insert and change my input:
ImageRepository.php
public function save($imageData)
{
$this->model->owner()->associate(auth()->user());
$this->model->type = $imageData->type;
$this->model->image_name = $imageData->image_url;
$this->model->save();
return $this->model;
}
Image.php
public function getImageNameAttribute($value)
{
$filePath = 'my_path';
// check if method was hit
logger()->info('getImageNameAttribute: '.$value);
// return default image if not found
if ($value == '' || $value == null || Str::contains($value, 'no-image.png') || !File::exists(public_path($filePath))) {
return asset('img/no-image.png');
} else {
return asset($filePath);
}
}
Input for $imageData->image_url:
https://some-random-image-url/image-name.jpg
Inserted data:
img/no-image.png
Excepted output (I have a function that extracts the name from URL):
image-name.jpg
When I check the log, I get the message:
getImageNameAttribute: https://some-random-image-url/image-name.jpg
Can someone explain to me if I'm doing something wrong or this is working as pretended?
I did solve the problem, I miss checked. Actually inserted record in my database is correct:
image-name.jpg
But I was checking dd() output after the insert and the value from there was not correct:
img/no-image.png
Because when dd() fires, the accessor is also fired (as we are retrieving data) and as the image was not downloaded it was actually showing correct data.
I have the following code in my controller:
for($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::updateOrCreate([
'doc_number' => $request['doc_number'][$I]
],
$tourist_to_update);
}
Each time updateOrCreate runs, it does 1 of 3 things:
Updates the model instance; OR
Creates and saves a new one; OR
Leaves everything unchanged (if model with such values already exists)
I need to check if updateOrCreate has done the first one (updated) and then execute some code.
How can I do it?
You can figure it out like this:
$tourist = Tourist::updateOrCreate([...]);
if(!$tourist->wasRecentlyCreated && $tourist->wasChanged()){
// updateOrCreate performed an update
}
if(!$tourist->wasRecentlyCreated && !$tourist->wasChanged()){
// updateOrCreate performed nothing, row did not change
}
if($tourist->wasRecentlyCreated){
// updateOrCreate performed create
}
Remarks
From Laravel 5.5 upwards you can check if updates have actually taken place with the wasChanged and isDirty method.
isDirty() is true if model attribute has been changed and not saved.
wasChanged() is true if model attribute has been changed and saved.
There is also a property (not method!) wasRecentlyCreated to check if user was created or not.
$user = factory(\App\User::class)->create();
$user->wasRecentlyCreated; // true
$user->wasChanged(); // false
$user->isDirty(); // false
$user = \App\User::find($user->id);
$user->wasRecentlyCreated; // false
$user->wasChanged(); // false
$user->isDirty(); // false
$user->firstname = 'Max';
$user->wasChanged(); // false
$user->isDirty(); // true
$user->save();
$user->wasChanged(); // true
$user->isDirty(); // false
//You can also check if a specific attribute was changed:
$user->wasChanged('firstname');
$user->isDirty('firstname');
You can checkout the link to the laravel's documentation for wasChanged and isDirty methods.
https://laravel.com/docs/8.x/eloquent#examining-attribute-changes or
https://laravel.com/docs/9.x/eloquent#examining-attribute-changes
It is pretty easy to determine if the function resulted in an update or an insert (check the wasRecentlyCreated property). However, when using that function, it is less easy to determine if the update actually happened (if the model exists but is not dirty, no update will be performed). I would suggest not using that function, and splitting out the functionality yourself.
This is the function definition:
public function updateOrCreate(array $attributes, array $values = [])
{
$instance = $this->firstOrNew($attributes);
$instance->fill($values)->save();
return $instance;
}
To integrate this into your code, I'd suggest something like:
for ($i=0; $i<$number_of_tourists; $i++) {
$tourist = Tourist::firstOrNew(['doc_number' => $request['doc_number'][$i]]);
$tourist->fill($tourist_to_update);
// if the record exists and the fill changed data, update will be performed
$updated = $tourist->exists && $tourist->isDirty();
// save the tourist (insert or update)
$tourist->save();
if ($updated) {
// extra code
}
}
Okay so I couldn't find a good answer for my scenario.
I was using: $this->created_at == $this->updated_at however I would sometimes update the record later in the request, which meant that 20% of the time the created_at and updated_at were about 1ms out.
To combat this I created something a little more relaxed which allows an extra second between creation and modification.
public function getRecentlyCreatedAttribute()
{
return $this->wasRecentlyCreated || $this->created_at == $this->updated_at || $this->created_at->diffInSeconds($this->updated_at) <= 1;
}
I can now call $this->recentlyCreated which will return true if there is a small difference in time (1 second).
Tbh this is the second time I've needed this in a project, I'm posting as I just ended up googling it and coming back to this thread looking for the same answer.
If someone has a more elegant solution, hmu.
#patricus below presented a working way to solve the problem.
though #TheFallen here gave a solution which uses Eloquent Events and seems more elegant:
Laravel Eloquent Events - implement to save model if Updated
The model attribute 'wasRecentlyCreated' would only be 'true' if it has just been created.
There is property named 'changes' in model (it is an array), that determines whether the model has been updated with new values or it has been saved as is without making any changes to its attribute.
Check the following code snippet:
// Case 1 : Model Created
if ($model->wasRecentlyCreated) {
} else { // Case 2 : Model Updated
if (count($model->changes)) { // model has been assigned new values to one of its attributes and saved successfully
} else { // model has NOT been assigned new values to one of its attributes and saved as is
}
}
Where and how I am overriding the save method in Joomla 3.0 custom component ?
Current situation:
Custom administrator component.
I have a list view that displays all people stored in table.
Clicking on one entry I get to the detailed view where a form is loaded and it's fields can be edited.
On save, the values are stored in the database. This all works fine.However, ....
When hitting save I wish to modify a field before storing it into the database. How do I override the save function and where? I have been searching this forum and googled quiet a bit to find ways to implement this. Anyone who give me a simple example or point me into the right direction ?
Thanks.
Just adding this for anyone who wants to know the answer to the question itself - this works if you explicitly wish to override the save function. However, look at the actual solution of how to manipulate values!
You override it in the controller, like this:
/**
* save a record (and redirect to main page)
* #return void
*/
function save()
{
$model = $this->getModel('hello');
if ($model->store()) {
$msg = JText::_( 'Greeting Saved!' );
} else {
$msg = JText::_( 'Error Saving Greeting' );
}
// Check the table in so it can be edited.... we are done with it anyway
$link = 'index.php?option=com_hello';
$this->setRedirect($link, $msg);
}
More details here: Joomla Docs - Adding Backend Actions
The prepareTable in the model (as mentioned above) is intended for that (prepare and sanitise the table prior to saving). In case you want to us the ID, though, you should consider using the postSaveHook in the controller:
protected function postSaveHook($model, $validData) {
$item = $model->getItem();
$itemid = $item->get('id');
}
The postSaveHook is called after save is done, thus allowing for newly inserted ID's to be used.
You can use the prepareTable function in the model file (administrator/components/yourComponent/models/yourComponent.php)
protected function prepareTable($table)
{
$table->fieldname = newvalue;
}
I have a custom module that displays data on my product pages. My module needs to get the current product id. I tried using:
Mage::registry('current_product');
This works on the first load but when I refresh, current_product no longer has data with full page caching on.
Any ideas?
The catalog product action controller isn't dispatched, when the full page cache handles the request (which makes sense to keep things fast).
So, the registry variables are never set.
I assume you are generating the block dynamically on the otherwise fully cached page.
My recommendation is to try to avoid expensive loads, since that would undermine the speed improvements by the full page cache. You really want to cache the block if at all possible, even if it's a separate cache entry for each customer and each product.
That said, here is how to do that:
In the container, implement the _getIdentifier() method:
protected function _getIdentifier()
{
return $this->_getCookieValue(Enterprise_PageCache_Model_Cookie::COOKIE_CUSTOMER, '');
}
Also expand the _getCacheId() method to include the return value of the method _getIdentifier() and a new placeholder attribute: product_id
protected function _getCacheId()
{
return 'HOMEPAGE_PRODUCTS' . md5($this->_placeholder->getAttribute('cache_id') . ',' . $this->_placeholder->getAttribute('product_id')) . '_' . $this->_getIdentifier();
}
Next, in the block class, extend the method getCacheKeyInfo(). All entries in the cache_key_info array with a string index are set on the placeholder as attributes. That is how we can pass the product id to the placeholder.
public function getCacheKeyInfo()
{
$info = parent::getCacheKeyInfo();
if (Mage::registry('current_product'))
{
$info['product_id'] = Mage::registry('current_product')->getId();
}
return $info;
}
Then enable the method _saveCache() by not overriding it in your container class and returning false.
So now, because the container returns a valid id from _getCacheId(), and _saveCache() is inherited from the parent class, the the block can be cached, and applied to the content in an efficient manner in Enterprise_PageCache_Model_Container_Abstract::applyWithoutApp().
You can set the lifetime of the cache entry by having your container extend from Enterprise_PageCache_Model_Container_Customer instead of Enterprise_PageCache_Model_Container_Abstract.
If you still need to pass the product_id to the block (even though it's cached now), you can do so in the _renderBlock() method of your container:
protected function _renderBlock()
{
$blockClass = $this->_placeholder->getAttribute('block');
$template = $this->_placeholder->getAttribute('template');
$block = new $blockClass;
$block->setTemplate($template)
->setProductId($this->_placeholder->getAttribute('product_id'));
return $block->toHtml();
}
you should have in the controller of your module something which init the registration of the current product. You will need to post between the page visited by a user the id of your product (recommended) or save it in a session. Here is an example of what you can do in a controller:
protected function _initAction(){
if(! Mage::registry('current_product')){
Mage::register('current_product', Mage::getModel('catalog/product'));
}
$id = $this->getRequest()->getParam('id');
if (!is_null($id)) {
$model = Mage::registry('current_product')->load($id);
if (! $model->getId()) {
$this->_getSession()->addError(Mage::helper('catalog')->__('This product item no longer exists'));
$this->_redirect('*/*/');
return;
}
}
return $this;
}
public function indexAction()
{
$this->_initAction();
// your code below
}