Laravel Soft Delete Unique column name - php

Suppose I have category table and I have used soft delete on it. Now first time I have added one category "Test" after that I have delete this category so it will update my deleted_at column from the database.
Now when again I am trying to add category with name "Test" it is saying me that this name has been taken. I have tried with rules which are posted Here.
But it is not working. I have used trait in my model. Below is my model code.
<?php
namespace App\Models;
use Illuminate\Support\Facades\Validator as Validator;
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Category extends \Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
/**
* Guarded fields which are not mass fillable
*
* #var array
*/
protected $guarded = array('id');
/**
* Name of the table used
*
* #var string
*/
protected $table = 'travel_categories';
/**
* Validating input.
*
* #param array $input
* #return mixed (boolean | array)
*/
public static function validate($input, $id = null) {
$rules = array(
'name' => array('required','min:2','max:100','regex:/[a-zA-z ]/','unique:travel_categories,name,NULL,id,deleted_at,NULL'),
'description' => 'Required|Min:2',
'image' => 'image'
);
if ($id) {
$rules['name'] = 'Required|Between:3,64|Unique:travel_categories,name,'.$id;
}
$validation = Validator::make($input,$rules);
return ($validation->passes())?true : $validation->messages();
}
}

Did you understand the soft deleting purpose? It will only flag the data to be inactive. It will be there though.
So, if you define the values of that column must be unique, it is right you could not create other one alike.
If it needs to be unique, so you should restore and update the data.
If it can have many ones, so you should remove the unique key applied on it (and call it by relationship for instance).
Look at: Laravel Eloquent Soft Deleting

First: I don't understand a couple of things. Are you trying to validate for create and update? Then why do you allow name to be of length 2 till 100 for creation, and only 3 till 64 for after updates?
Second: I recommend dropping this:
protected $dates = ['deleted_at'];
I don't see the goal of that.
Third, and I'm getting to the point here, what are you trying to do? I guess, what you are trying to do with this filter 'unique:travel_categories,name,NULL,id,deleted_at,NULL' is to check the uniqueness of the name among the active categories. In that case, that should work.

As noted above, a unique index on [category, deleted_at] will not work because when deleted_at is null, many SQL RDBMS will allow multiple records to be inserted despite a unique index existing.
In case anyone is interested I have now created a Laravel extension to handle the SQL unique-index constraints correctly: https://packagist.org/packages/tranzakt/laravel-softdeletesunique
If anyone tries this, please give feedback on Github, thanks.

I know this question is old, but I had a similar issue and I stumbled upon this. I wanted to mention how I fixed it for anyone, who is reading it in the future. The problem I had was that Laravel did not allow me to insert a value in a unique column when there was an old record with the same value, but was deleted using soft_delete.
To summarize, the goal is to ignore old soft deleted records for a unique column when inserting a new record. The solution I found is in the migration for the table. For example, let us assume we have these columns:
category - unique
deleted_at - keeps tracks of the deleted rows
Both should be specified as unique in the migration like so:
Schema::create('table_name', function (Blueprint $table) {
$table->string("category");
$table->softDeletes();
$table->unique(["category", "deleted_at"]);
});
Side note: If you already have the table like I did, you need to change the migration and create the table again (obviously the data will be lost):
Remove the table
Change the migration
Remove the record about it from the migrations table
run "php artisan migrate" to create the table again

Related

How do i solve General error: 1364 - Laravel

Screenshot of the error page
How do i go about this please? I created another table in my db where i generate OTPs and send to the users. in my table i made user_id and id(primary) but due to the nature of my code and how i want the site to function, i set user_id as a reference of id so that whenever OTP is generated, it will be the current user's ID
It seems there's no default value for user_id so i did ;
UserOTP::create(["user_id"=> auth()->id()]);
but its not working, what is wrong with my code please?
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserOtpsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('user_otps', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->integer('code');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('user_otps');
}
}
This is the db migration file
It's because you haven't set user_id field in $fillable property. Hence, Laravel ignored provided value for this field when you are using create() method.
However, before using the create method, you will need to specify
either a fillable or guarded property on your model class. These
properties are required because all Eloquent models are protected
against mass assignment vulnerabilities by default.
Related document
protected $fillable = ['code' , 'user_id'];
It's a SQL error not a PHP one. You're trying to insert a new row without a user_id but there's no default value set for that column, so it doesn't know what to set it as.
In your migration class, try giving it a default value:
$table->unsignedBigInteger('user_id')->default(null);
In any case, it also seems like you're not inserting with a user_id, only code, so you need to probably ensure that field can be persisted.
Edit: Yes, now you've posted your model, the problem is you haven't allowed the user_id field to be fillable:
// UserOtp.php
protected $fillable = ['code' , 'user_id'];
Okay so i found it. Instead of keeping the answer to myself, i wanna share to whoever that may have this issue in the future.
so i made a function called rules where i forgot to add user_opts to code .
public function rules()
{
return [
'otpcode' => ['required','numeric',Rule::exists('user_otps','code')->where(function($query){
$query->whereUserId(auth()->id());
})]
];
adding user_otps to code actually solved it and everything is working now. The main problem wasnt this but rather, as the previous answers were saying. My user_id field wasnt receiving a value on submit.
Happy to resolve this issue and thanks to whoever helped

Laravel trying to save $appends attributes

I am coding on Laravel 6, and stumbled up on this problem. I have accessors for a field that i need in my model in some cases, but it does not exist in my database. As i read in documentation, the variable $appends make the field in question to be serialized with the model, but not saved in database.
Then, when i update or save entries of the specific model in database, Laravel gives me back an error saying that field x is not present in field list. I searched here, googled a lot, but didn't found any answer.
This pluggable_data field is not sent by client-side. I create it on server-side because i need its information to do some tasks. But it is not relevant enough to create a column in DB just to store it.
Model
/**
* #var array|null
*/
protected $appends = [
'pluggable_data'
];
/**
* #param array $pluggableData
*/
public function setPluggableDataAttribute(array $pluggableData)
{
$this->attributes['pluggable_data'] = $pluggableData;
}
/**
* #return array
*/
public function getPluggableDataAttribute(): array
{
return $this->attributes['pluggable_data'] ?? []; // Sometimes there is no pluggable data, then i return an empty array
}
Where the error occurs
$activity->setRegisterDate($fields['register_date']);
$activity->setExtraAttribute($fields['extra']);
$activity->update(); <----- Here
return $activity;
The error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'pluggable_data' in 'field list'
Summarizing what i want (in case that you have a better idea): i want a field that will be present in model serializations, even if it is an empty array, but that does not be saved on database.
As #nay said in the comments, all i had to do was to replace the $this->attributes['pluggable_data'] by a real variable, like $this->pluggable_data. This way, Laravel will not think that it is a database column

Laravel 5.0 get the id of the row inserted in a pivot table after attach

I have a newsletter and block model that has a many to many relation. When I attach a block to a newsletter I want the id of the row that was insterted in the pivot table.
This is what I tried but results in "null"
$newsletter = Newsletter::findOrFail($newsletterId);
$newsletterBlock = $newsletter->blocks()->attach($blockId);
dd($newsletterBlock);
Although this question is old, however nobody gave the right answer. If you are in a situation that your pivot table has a primary key.
Using withPivot method is useful when you want get the id when you load the related model. However when you attach something you can not get the pivot_id;
In these situations best thing to do is to use a model that is connected to the pivot table, then only thing you need to do is inserting into pivot table using its model instead of attaching two models together.
in this example, first create a model called BlockNewsLetter if it is necessary fill the $table and $fillable attributes of the model and then you are good to go.
$newsletter = Newsletter::findOrFail($newsletterId);
$blockNewsLetterPivot = BlockNewsLetter::updateOrCreate([
'block_id' => $block_id ,
'newsLetter_id' => $newsletter->id
]);
$inserted_pivot_id = $blockNewsLetterPivot->id;
Try this, haven't test it but this should work:
/** Newsletter.php */
public function blocks()
{
return $this->belongsToMany('App\Block')
->withPivot('id');
}
Then in your controller:
$newsletter = Newsletter::findOrFail($newsletterId);
$newsletter->blocks()->attach($blockId);
// then you could:
foreach($newsletter->blocks as $block)
{
dd($block->pivot->id);
}
Check this section of the docs.

Disable Doctrine foreign key constraint

I have a relationship on one of my models:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $parentPage;
And when I delete the parent page, I get this error:
Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
Basically my models are a page, and page revision. When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
How can I do this with Doctrine?
By definition you cannot delete the record that the foreign key is pointing at without setting the key to null (onDelete="SET NULL") or cascading the delete operation (There are two options - ORM Level: cascade={"remove"} | database level: onDelete="CASCADE"). There is the alternative of setting a default value of a still existing record, but you have to do that manually, I don't think Doctrine supports this "out-of-the-box" (please correct me if I am wrong, but in this case setting a default value is not desired anyway).
This strictness is reflecting the concept of having foreign key constraints; like #Théo said:
a FK is to ensure data consistency.
Soft delete (already mentioned) is one solution, but what you could also do is add an additional removed_page_id column that you sync with the page_id just before you delete it in a preRemove event handler (life cycle callback). Whether such information has any value I wonder but I guess you have some use for it, otherwise you wouldn't ask this question.
I am definitely not claiming this is good practice, but it is at least something that you can use for your edge case. So something in the line of:
In your Revision:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $parentPage;
/**
* #var int
* #ORM\Column(type="integer", name="removed_page_id", nullable=true)
*/
protected $removedPageId;
And then in your Page:
/**
* #ORM\PreRemove
*/
public function preRemovePageHandler(LifecycleEventArgs $args)
{
$entityManager = $args->getEntityManager();
$page = $args->getEntity();
$revisions = $page->getRevisions();
foreach($revisions as $revision){
$revision->setRemovedPageId($page->getId());
$entityManager->persist($revision);
}
$entityManager->flush();
}
Alternatively you could of course already set the correct $removedPageId value during construction of your Revision, then you don't even need to execute a life cycle callback on remove.
I solved this by overriding one doctrine class in symfony 4.3, it looks like this for me:
<?php declare(strict_types=1);
namespace App\DBAL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
/**
* Class MySQLPlatformService
* #package App\DBAL
*/
class MySQLPlatformService extends MySQLPlatform
{
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyConstraints(): bool
{
return false;
}
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyOnUpdate(): bool
{
return false;
}
}
You can disable the exporting of foreign keys for specific models:
User:
attributes:
export: tables
columns:
Now it will only export the table definition and none of the foreign keys. You can use: none, tables, constraints, plugins, or all.
You are explicitly asking for data inconsistency, but I'm pretty sure you really don't want that. I can't think of a situation where this would be defensible. It is a bad practice and definitely will cause problems. For example: what is the expected result of $revision->getPage()?
There is a very simple and elegant solution: softdeletable. It basically adds an attribute to your entity (in other words: adds column to your table) named deletedAt to store if (or better: when) that entity is deleted. So if that attribute is null, the entity isn't deleted.
The only thing you have to do is add this bundle, add a trait to your entity (Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity) and update your database. It is very simple to implement: this package will do the work for you. Read the documentation to understand this extension.
Alternatively, you can add an 'enabled' boolean attribute or a status field (for example 'published', 'draft', 'deleted').
When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
I think you already got your answer: Doctrine won't do that, simply because it's alien to the notion of Foreign Keys. The principle of a FK is to ensure data consistency, so if you have a FK, it must refer to an existing ID. On delete, some DB engine such as InnoDB for MySQL allow you to put an FK to NULL (assuming you did made the FK column nullable). But referring to an inexistent ID is not doable, or it's not a FK.
If you really want to do it, don't use Doctrine for this specific case, it doesn't prevent you to use Doctrine elsewhere in your codebase. Another solution is to just drop the FK constraint manually behind or use a DB statement before your query to skip the FK checks.

Integrity constraint violation: 1062 Duplicate entry '4974-134' for key 'UNQ_CATALOG_PRODUCT_SUPER_ATTRIBUTE_PRODUCT_ID_ATTRIBUTE_ID'

I am getting the above error in Magento when adding a configurable product (before creating simple products)
This has worked but for some reason it is now failing.
The key value 4974-134 doesn't even exist in the table:
I've tried re-creating the table. I''ve cleare cache/log tables/re-indexed and nothing seems to work - each time the 4974 (product/entity_id) increments by 1 implying it is being created in the catalog_product_entity table but it isn't:
The only way that I could resolve this eventually was to extend/overwrite the Product model _afterSave function in a new module (make sure the new class extends extends Mage_Catalog_Model_Product).
Like so:
/**
* Saving product type related data and init index
*
* #return Mage_Catalog_Model_Product
*/
protected function _afterSave()
{
$this->getLinkInstance()->saveProductRelations($this);
if($this->getTypeId() !== 'configurable')
{
$this->getTypeInstance(true)->save($this);
}
/**
* Product Options
*/
$this->getOptionInstance()->setProduct($this)
->saveOptions();
$result = parent::_afterSave();
Mage::getSingleton('index/indexer')->processEntityAction(
$this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE
);
return $result;
}
The key bit being:
if($this->getTypeId() !== 'configurable')
{
$this->getTypeInstance(true)->save($this);
}
}
It looks like for some reason, when creating the configurable product it was trying to save an object that already existed in the resource adapter possibly - Some thoughts on this would be appreciated.
It's a combination of the product_id and the attribute_id. Whats interesting is 4795 already exists in there.
I had this issue once after Host Gator transferred a Magento site for me.
Find out which tables are being used as the FK for these two fiends and make sure that the next increment ID isn't lower than the highest in this table.
In my case, one of the tables auto_increment ID was reset to 0 so it was trying to create a "1", but that foreign key was already used in the offending table.
Hope this helps.

Categories