Kiedy usuwam wiersz przy użyciu tej składni:
$user->delete();
Czy istnieje sposób na dołączenie jakiegoś wywołania zwrotnego, aby np. Robił to automatycznie:
$this->photo()->delete();
Najlepiej wewnątrz klasy model.
Kiedy usuwam wiersz przy użyciu tej składni:
$user->delete();
Czy istnieje sposób na dołączenie jakiegoś wywołania zwrotnego, aby np. Robił to automatycznie:
$this->photo()->delete();
Najlepiej wewnątrz klasy model.
Odpowiedzi:
Uważam, że jest to doskonały przypadek użycia dla wydarzeń Eloquent ( http://laravel.com/docs/eloquent#model-events ). Możesz użyć zdarzenia „deleting”, aby wyczyścić:
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
// this is a recommended way to declare event handlers
public static function boot() {
parent::boot();
static::deleting(function($user) { // before delete() method call this
$user->photos()->delete();
// do the rest of the cleanup...
});
}
}
Prawdopodobnie powinieneś również umieścić całość wewnątrz transakcji, aby zapewnić referencyjną integralność.
foreach($user->photos as $photo)
, a następnie $photo->delete()
upewnić się, że każde dziecko ma usunięte swoje dzieci na wszystkich poziomach, zamiast tylko jednego, jak to się dzieje z jakiegoś powodu.
Photos
ma tags
i zrobisz to samo w Photos
modelu (tj. W deleting
metodzie $photo->tags()->delete();
:), nigdy nie zostanie wyzwolony. Ale jeśli zrobię for
pętlę i zrobię coś takiego, for($user->photos as $photo) { $photo->delete(); }
to tags
również zostaną usunięte! tylko do Twojej wiadomości
Możesz to ustawić w swoich migracjach:
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Źródło: http://laravel.com/docs/5.1/migrations#foreign-key-constraints
Możesz także określić żądane działanie dla właściwości „przy usuwaniu” i „przy aktualizacji” ograniczenia:
$table->foreign('user_id') ->references('id')->on('users') ->onDelete('cascade');
Uwaga : ta odpowiedź została napisana dla Laravel 3 . Zatem może, ale nie musi, działać dobrze w nowszej wersji Laravel.
Możesz usunąć wszystkie powiązane zdjęcia przed faktycznym usunięciem użytkownika.
<?php
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
public function delete()
{
// delete all related photos
$this->photos()->delete();
// as suggested by Dirk in comment,
// it's an uglier alternative, but faster
// Photo::where("user_id", $this->id)->delete()
// delete the user
return parent::delete();
}
}
Mam nadzieję, że to pomoże.
$this->photos()->delete()
. photos()
Zwraca obiekt kreator zapytań.
Relacja w modelu użytkownika:
public function photos()
{
return $this->hasMany('Photo');
}
Usuń rekord i powiązane:
$user = User::find($id);
// delete related
$user->photos()->delete();
$user->delete();
Istnieją 3 sposoby rozwiązania tego problemu:
1. Używanie Eloquent Events On Model Boot (ref: https://laravel.com/docs/5.7/eloquent#events )
class User extends Eloquent
{
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->delete();
});
}
}
2. Korzystanie z Eloquent Event Observers (patrz: https://laravel.com/docs/5.7/eloquent#observers )
W swoim AppServiceProvider zarejestruj obserwatora w następujący sposób:
public function boot()
{
User::observe(UserObserver::class);
}
Następnie dodaj klasę Observer w następujący sposób:
class UserObserver
{
public function deleting(User $user)
{
$user->photos()->delete();
}
}
3. Korzystanie z ograniczeń klucza obcego (patrz: https://laravel.com/docs/5.7/migrations#foreign-key-constraints )
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Począwszy od Laravel 5.2, dokumentacja stwierdza, że tego typu programy obsługi zdarzeń powinny być zarejestrowane w AppServiceProvider:
<?php
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
User::deleting(function ($user) {
$user->photos()->delete();
});
}
Przypuszczam nawet, że przeniosę je do oddzielnych klas zamiast domknięć dla lepszej struktury aplikacji.
photos()
, musisz również zachować ostrożność - ten proces nie usunie wnuków, ponieważ nie ładujesz modeli. Będziesz musiał zapętlić photos
(uwaga, nie photos()
) i uruchomić delete()
na nich metodę jako modele, aby uruchomić zdarzenia związane z usuwaniem.
Lepiej będzie, jeśli zmienisz tę delete
metodę. W ten sposób możesz włączyć transakcje DB do delete
samej metody. Jeśli korzystasz ze sposobu zdarzenia, będziesz musiał pokrywać wywołanie delete
metody transakcją DB za każdym razem, gdy ją wywołasz.
W Twoim User
modelu.
public function delete()
{
\DB::beginTransaction();
$this
->photo()
->delete()
;
$result = parent::delete();
\DB::commit();
return $result;
}
W moim przypadku było to całkiem proste, ponieważ moje tabele bazy danych to InnoDB z kluczami obcymi z Cascade on Delete.
Więc w tym przypadku, jeśli twoja tabela zdjęć zawiera odniesienie do klucza obcego dla użytkownika, to wszystko, co musisz zrobić, to usunąć hotel, a czyszczenie zostanie wykonane przez Bazę Danych, baza danych usunie wszystkie rekordy zdjęć z danych baza.
Iterowałbym przez kolekcję, odłączając wszystko przed usunięciem samego obiektu.
oto przykład:
try {
$user = user::findOrFail($id);
if ($user->has('photos')) {
foreach ($user->photos as $photo) {
$user->photos()->detach($photo);
}
}
$user->delete();
return 'User deleted';
} catch (Exception $e) {
dd($e);
}
Wiem, że to nie jest automatyczne, ale jest bardzo proste.
Innym prostym podejściem byłoby dostarczenie modelowi metody. Lubię to:
public function detach(){
try {
if ($this->has('photos')) {
foreach ($this->photos as $photo) {
$this->photos()->detach($photo);
}
}
} catch (Exception $e) {
dd($e);
}
}
Następnie możesz po prostu zadzwonić tam, gdzie potrzebujesz:
$user->detach();
$user->delete();
Lub możesz to zrobić, jeśli chcesz, po prostu inna opcja:
try {
DB::connection()->pdo->beginTransaction();
$photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
$user = Geofence::where('id', '=', $user_id)->delete(); // Delete users
DB::connection()->pdo->commit();
}catch(\Laravel\Database\Exception $e) {
DB::connection()->pdo->rollBack();
Log::exception($e);
}
Uwaga, jeśli nie używasz domyślnego połączenia laravel db, musisz wykonać następujące czynności:
DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();
Aby uszczegółowić wybraną odpowiedź, jeśli Twoje relacje mają również związki podrzędne, które należy usunąć, musisz najpierw pobrać wszystkie rekordy relacji podrzędnych, a następnie zadzwonić do delete()
metodę, aby zdarzenia usuwania również zostały poprawnie .
Możesz to łatwo zrobić z wiadomościami wyższego rzędu .
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* @return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get()->each->delete();
});
}
}
Możesz również poprawić wydajność, wysyłając zapytania tylko do kolumny ID relacji:
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* @return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get(['id'])->each->delete();
});
}
}
tak, ale jak @supersan stwierdził u góry w komentarzu, jeśli usuniesz () w QueryBuilder, zdarzenie modelu nie zostanie uruchomione, ponieważ nie ładujemy samego modelu, a następnie wywołujemy metodę delete () w tym modelu.
Zdarzenia są uruchamiane tylko wtedy, gdy używamy funkcji usuwania na wystąpieniu modelu.
Więc to powiedział:
if user->hasMany(post)
and if post->hasMany(tags)
aby usunąć tagi postów podczas usuwania użytkownika, musielibyśmy powtórzyć $user->posts
i zadzwonić$post->delete()
foreach($user->posts as $post) { $post->delete(); }
-> to uruchomi zdarzenie usuwania w Post
VS
$user->posts()->delete()
-> to nie uruchomi zdarzenia kasującego w poście, ponieważ tak naprawdę nie ładujemy Post Modelu (uruchamiamy tylko SQL typu: DELETE * from posts where user_id = $user->id
a zatem model Post nie jest nawet ładowany)
Możesz użyć tej metody jako alternatywy.
To, co się stanie, to pobranie wszystkich tabel powiązanych z tabelą użytkowników i usunięcie powiązanych danych za pomocą zapętlenia
$tables = DB::select("
SELECT
TABLE_NAME,
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'users'
");
foreach($tables as $table){
$table_name = $table->TABLE_NAME;
$column_name = $table->COLUMN_NAME;
DB::delete("delete from $table_name where $column_name = ?", [$id]);
}
first()
do zapytania, aby uzyskać dostęp do zdarzenia modelu, np.User::where('id', '=', $id)->first()->delete();
Źródło