Magento model delete function - magento

I have Model class which extends Mage_Core_Model_Abstract. I have a resource which extends Mage_Core_Model_Mysql4_Abstract. Now I am deleting from controller with the following code:
public function deleteAction()
{
$params = $this->getRequest()->getParams();
$blogpost = $this->_createModel();
$blogpost->setId($params['id']);
$blogpost->delete();
echo 'Post with id ' . $params['id'] . ' is deleted';
}
The problem is there is no error shown when I supply non existing ids. What to do ? Do I have to load that id and check whether it exists or not? or should I use the try/catch. I used try and catch but did not give any results.

Of course it does not throw an exception, the DELETE statement gets executed without errors eventually, it only has an affected row count of 0.
Unfortunately the row count gets lost here (Mage_Core_Model_Resource_Db_Abstract):
/**
* Delete the object
*
* #param Varien_Object $object
* #return Mage_Core_Model_Resource_Db_Abstract
*/
public function delete(Mage_Core_Model_Abstract $object)
{
$this->_beforeDelete($object);
$this->_getWriteAdapter()->delete(
$this->getMainTable(),
$this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . '=?', $object->getId())
);
$this->_afterDelete($object);
return $this;
}
because the return value of _getWriteAdapter()->delete() is not used anywhere
It looks like you will indeed have to load your model before deleting it, or at least check for its existence via the ressource collection.

Related

Scheduled Command to update records

Morning all,
I am trying to create a command that I can schedule to check if a certification date has expired and if it has, update the boolean from 0 to 1. I have never used commands before and I have read the OctoberCMS documentation but I found it confusing.
If anyone could help me, that would be perfect.
Here is what I have so far.
<?php
namespace Bitpixlimited\Concert\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use BitpixLimited\ConCert\Models\Certification;
use Carbon\Carbon;
/**
* CheckExpiredCertifications Command
*/
class CheckExpiredCertifications extends Command
{
/**
* #var string name is the console command name
*/
protected $name = 'concert:checkexpiredcertifications';
/**
* #var string description is the console command description
*/
protected $description = 'No description provided yet...';
/**
* handle executes the console command
*/
public function handle()
{
$certifications = Certification::all();
$date = now();
$expiredValue = '1';
foreach ($certifications as $certification) {
if ($certification->expiry_date < $date) {
$certification->status = $expiredValue;
}
$certification->save();
}
}
/**
* getArguments get the console command arguments
*/
protected function getArguments()
{
return [];
}
/**
* getOptions get the console command options
*/
protected function getOptions()
{
return [];
}
}
Take a look at this code:
public function handle()
{
$certifications = Certification::query()
->where('expiry_date', '<', now())
->update(['status' => '1'])
}
It does what you are trying to achieve, it's a simplified version of your code and it is more performant.
We don't actually get the records, we update them directly
We update all records that have a expiry_date before now()
All these records now have the status equals to 1
Since we don't store the records in memory and we don't need to "build" the Collection, it's far better performance wise.
The drawback is that you lose model events (if you declared any) and mutators, but I assume that's not the case here.
If you need to access all models mutators, methods, events (now or in the future), then use the following code:
public function handle()
{
$certifications = Certification::query()
->where('expiry_date', '<', now())
->each(function(Certification $certification){
$certification->status = '1';
$certification->save();
});
}
The main difference is that we actually retrieve the records and build all the Certification instances. It gives you more capabilities but the performances will take a hit.
There are more optimized ways to do this, especially if you have a large number of rows, but this is another topic.
You should run this command in your scheduler at the frequency you wish, for instance every minute:
protected function schedule(Schedule $schedule)
{
$schedule->command('concert:checkexpiredcertifications')->everyMinute();
}
Every minute, we will update all records that have expiry_date in the past and set their status to '1'.
Of course, you must have a working scheduler to do this, but that's a little bit off topic here (docs here: https://laravel.com/docs/8.x/scheduling#running-the-scheduler).

Laravel/Livewire: Use withTrashed() on model route binding on to show deleted records

In the list I display the latest topic, including those that is deleted.
function latest()
{
return Topic::withTrashed()->latest();
}
For displaying a single topic I have a Livewire component with that topic passed into it.
class ShowTopic extends Component
{
public $topic;
public function mount(Topic $topic)
{
$this->topic = $topic;
}
public function render()
{
return view('livewire.show-topic', [
'topic' => $this->topic,
]);
}
}
But when I go to a single topic that is deleted, it doesn't show. How can I use withTrashed() on model route bindings to show deleted records with my Livewire component?
You can overwrite the resolveRouteBinding() method on your Eloquent model, and conditionally remove the SoftDeletingScope global scope.
Here I'm using a policy for that model to check if I can delete the model - and if the user can delete it, they can also see it. You could implement any logic you want, or remove the global scope for all requests if that is more suitable for your application.
use Illuminate\Database\Eloquent\SoftDeletingScope;
class Topic extends Model {
// ...
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
// If no field was given, use the primary key
if ($field === null) {
$field = $this->getKey();
}
// Apply where clause
$query = $this->where($field, $value);
// Conditionally remove the softdelete scope to allow seeing soft-deleted records
if (Auth::check() && Auth::user()->can('delete', $this)) {
$query->withoutGlobalScope(SoftDeletingScope::class);
}
// Find the first record, or abort
return $query->firstOrFail();
}
}

Laravel Eloquent collection of mixed models

I have a database with a table called files. Within that, we have the following structure -
- id
- parent_id (nullable)
- name
- type (enum: File or Folder)
- created_at
- updated_at
I then have two models, one called File and one called Folder. Folder extends File. Is there a way that when I call File::all(), for example, I can utilize Eloquent to map the respective models based on the databases type field?
Eloquent returns collection instances, so one way would be to call map() and have that return the appropriate objects for each item, eg, if it's a file just return the file, whereas if it's a folder populate a new Folder instance and return it.
Or you could have File and Folder be models that work off the same table, with global scopes used to limit the query set by type, then call all() on both of them and merge them.
But I think the best thing to do with them is make them a single model, that behaves differently based on the type. Put any differing functionality in methods on the model so it can be treated the same regardless of type by calling those methods. I think that's the safer option in that you're making your models polymorphic - they can be treated the same regardless of type.
I've been able to work out the answer by extending Laravel Models newFromBuilder method.
Here is my class -
class File {
public static $types = ['File', 'Folder'];
/**
* Create a new model instance that is existing.
*
* #param array $attributes
* #param null $connection
*
* #return Model|string
*/
public function newFromBuilder($attributes = [], $connection = null)
{
$model = $this->newInstanceFromType($attributes->type);
$model->exists = true;
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
/**
* Determine our model instance based on the type field.
*
* #param string $type
*
* #return mixed
*/
private function newInstanceFromType(string $type)
{
if (!in_array($type, static::$types)) {
throw new InvalidArgumentException('$type must be one of static::$types');
}
$model = 'App\Models\\' . $type;
return new $model;
}
}
This will then return either a File or Folder model instance depending on what the type enum is from the database.
Thanks all for the input!

How to delete the first row

I have some trouble with Laravel SQL builder. I want to delete the first row. I am using the below code:
DB::table('ahah')->first()->delete().
In the controller, I've already imported DB class using the statement use Illuminate\Support\Facades\DB. Can you tell me what's the problem and how to fix it?
It doesn't work because DB::table('ahah')->first() is returning a stdClass object. And of course, stdClass has no delete() method.
So you will need to get the first item of your table with SQL statements:
// Supposing your primary key column is called 'id'
DB::table('ahah')
->orderBy('id')
->limit(1)
->delete();
If are working in Laravel then it's better to use Eloquent ORM by creating Model.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// Code Stuff
}
fetch the first records and delete
$user = \App\Models\User::orderBy('id')->first()->delete();
ModelName: Change with your model
$item = ModelName::orderBy('id', 'ASC')->first();
$item->delete();
Use the oldest method for getting the first record:
$ahah_first = \DB::table('ahah')->oldest()->first();
\DB::table('ahah')->where('id', $ahah_first->id)->delete();
As others answered, your requirement should be fulfilled as you described in your question.
I'm just adding a cleaner technique to delete the FIRST row of the table. Create the model (made using Eloquent), then add a scope method which will return the first row of the table and then you just need to call delete() on the query builder.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class FooBarModel extends Model
{
protected $table = 'foo_bar';
/**
* Scope a query to retrieve first row.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeFirstRow($query)
{
return $query->orderBy('id')->first(); // Assuming id is the name of primary key column in the table
}
}
Then you may delete the first row as:
FooBarModel::firstRow()->delete(); // This will delete the very first available row every time it is called
However, handling the exceptions is always a good practice. Here if no row exists in the table, then calling the above statement will raise an exception as the query builder object will return null. So better first check if the result is not null then only call delete() method.
$firstRow = FooBarModel::firstRow();
if (!is_null($firstRow)) {
$firstRow->delete();
}
Similarly, you can create other scopes as well to delete the last row, last to the last row, etc.
If you want to go for a more cleaner way than this then you can just write a method in the Model itself and write all code to delete there.
/**
* #return bool
*/
public static function deleteFirstRow()
{
$firstRow = self::firstRow();
if (!is_null($firstRow)) {
$firstRow->delete();
return true;
}
return false;
}
Then call deleteFirstRow() to delete first row.
FooBarModel::deleteFirstRow();
Hope this helps.

Laravel Model conditional formatting

I have a database and model called Vote_actions that looks like this:
id
group_id
user_id
action_type
anonymous (boolean)
User can ask to be anonymous (that would make the boolean value to be true).If that is the case, I want to change the group_id and user_id from the returned model to -1.
Is there a way in laravel that I can do it ?
I know this question is old. I was looking for a way to hide some fields on certain conditions, external conditions like Auth Roles, and internal conditions like Model attributes, and I found a very flexible way to hide them.
And since I saw the other OP's duplicated post Laravel Hidden Fields On Condition asking for hiding field instead, So I'm gonna share it with you.
I know a mutator can change the value of its field, but to Hide it, you need :
the $hidden array attribute
the constructor __Construct() (optional)
to override method newFromBuilder method of Laravel Model
Here are the processes in the Model app\Vote_actions.php:
Hidden. Let's say you normally want to hide the fields created_at and updated_at of Laravel, you use:
protected $hidden = ['created_at', 'updated_at'];
External Conditions. Now let's say if the Authenticated User is Staff you want to unhide them:
public function __Construct()
{
parent::__construct();
if(\Auth::check() && \Auth::user()->isStaff()) {
// remove all fields so Staff can access everything for example
$this->hidden = [];
} else {
// let's hide action_type for Guest for example
$this->hidden = array_merge($this->hidden, ['action_type'];
}
}
Internal Conditions Let's say now you want to hide anonymous field is its value is true:
/**
* Create a new model instance that is existing.
*
* #param array $attributes
* #param array $connection
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function newFromBuilder($attributes = array(), $connection = null)
{
$instance = parent::newFromBuilder($attributes, $connection);
if((bool)$instance->anonymous === true) {
// hide it if array was already empty
// $instance->hidden = ['anonymous'];
// OR BETTER hide single field with makeHidden method
$instance->makeHidden('anonymous');
// the opposite is makeVisible method
}
return $instance;
}
You can't play with hidden attributes and method inside mutators, that's their weakness when we need to hide instead of changing values.
But in any case, understand that calling modification on high load of hundredths of rows can be costly in time.
You are leaning towards an edge case, with special conditions.
Make use of accessors:
class VoteActions extends \Eloquent {
public $casts = [
'anonymous' => 'boolean'
];
...
/**
* Accessors: Group ID
* #return int
*/
public function getGroupIdAttribute()
{
if((bool)$this->anonymous === true) {
return -1;
} else {
return $this->group_id;
}
}
/**
* Accessors: User ID
* #return int
*/
public function getUserIdAttribute()
{
if((bool)$this->anonymous === true) {
return -1;
} else {
return $this->user_id;
}
}
}
Official Documentation: https://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
However, i would recommend that you set the value in the database directly to -1 where necessary so as to preserve the integrity of your application.
Of course you can easily do that. Read about accessors (getters):
https://laravel.com/docs/5.1/eloquent-mutators
Example:
function getUserIdAttribute()
{
return $this->anonymous ? -1 : $this->user_id;
}
function getGroupIdAttribute()
{
return $this->anonymous ? -1 : $this->group_id;
}

Resources