Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?
  • YardımLaravel
  • Laravel Model Olaylarında (created, saved) İlişkili Veriler Neden Boş Geliyor?

Merhaba,
Laravel'de with(), load(), loadMissing() gibi eager loading yöntemlerini kullanmama rağmen ilişkili veriler gelmiyor. Spatie Media Library ve categories ilişkisini içeren bir Post modelim var.

Aşağıdaki kodu saved olayında çalıştırıyorum:

static::saved(function ($post) {
    $freshPost = Post::with(['media', 'categories'])->find($post->id);

    \Log::info('Post created.', [
        'post_id' => $freshPost->id,
        'media' => $freshPost->media,
        'categories' => $freshPost->categories
    ]);
});

Farklı Denediğim yöntemler:
$post->load(['media', 'categories']) → Çalışmadı
$post->loadMissing(['media', 'categories']) → Çalışmadı

{
    "post_id": 165,
    "media": {
        "Spatie\\MediaLibrary\\MediaCollections\\Models\\Collections\\MediaCollection": []
    },
    "categories": {
        "Illuminate\\Database\\Eloquent\\Collection": []
    }
}

    macintyre Aşağıdaki örneklerde saved olayı ateşlenmez:

    Post::insert($data);
    
    Post::where('is_active', false)->update($data);

    çünkü QueryBuider kullanarak düz SQL sorgusu yapıyorsunuz. Eventların ateşlenebilmesi için model ile çalışmanız lazım. Mesela

    // $post bir Post modeli olmak üzere
    $post = Post::find($id);
    
    // saving, saved, updating, updated ateşlenir
    $post->update($data);
    
    // deleting, deleted ateşlenir
    $post->delete();

    Bunlara dikkat edin.

      mgsmus Olay ateşleniyor o durumda sorun yok, sorun ateşlendiği fonksiyonda Post'un ilişkilerini çekemiyor.
      Tinkerwell üzerinden ilişkilerde sorun olmadığını şöyle doğrulayabilirim:

      use App\Models\Post;
      
      $latestPost = Post::factory()
        ->create()
        ->first();
      
      return [
        "latest_posts_media" => $latestPost->media,
        "latest_posts_categories" => $latestPost->categories
      ];
      [
          "latest_posts_media" => Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection {#2884
            all: [
              Spatie\MediaLibrary\MediaCollections\Models\Media {#2914
                id: 40,
                model_type: "App\Models\Post",
                model_id: 46,
                uuid: "7dcf4a64-9978-4e14-aeaf-39bd89b42719",
                collection_name: "post",
                name: "1316",
                file_name: "1316.jpeg",
                mime_type: "image/jpeg",
                disk: "custom_public",
                conversions_disk: "custom_public",
                size: 88288,
                manipulations: "[]",
                custom_properties: "[]",
                generated_conversions: "[]",
                responsive_images: "[]",
                order_column: 1,
                created_at: "2025-02-17 14:01:52",
                updated_at: "2025-02-17 14:01:52",
                +original_url: "https://05-example-project.web-artisan//custom-folder/40/1316.jpeg",
                +preview_url: "",
              },
            ],
          },
          "latest_posts_categories" => Illuminate\Database\Eloquent\Collection {#2887
            all: [
              App\Models\Category {#2868
                id: 47,
                name: "fugiat",
                sort_order: 47,
                status: 1,
                created_at: "2025-02-16 21:01:00",
                updated_at: "2025-02-16 21:01:00",
                pivot: Illuminate\Database\Eloquent\Relations\Pivot {#2882
                  post_id: 46,
                  category_id: 47,
                },
              },
            ],
          },
        ]

        mgsmus Kuyruk kullanmıyorum, paylaşımlı sunucuda barındırıyorum web siteyi. Kullanma imkanı yok

          mgsmus

          Post

          class Post extends Model implements HasMedia
          {
              use HasFactory;
              use InteractsWithMedia;
              use Searchable;
          
              public function toSearchableArray()
              {
                  return [
                      'title' => $this->title,
                  ];
              }
          
              protected static function boot()
              {
                  parent::boot();
          
                  // Varsayılan olarak ilişkili verileri yükle
                  static::addGlobalScope('eagerLoadRelations', function ($query) {
                      $query->with(['media', 'categories']);
                  });
                  static::saved(function ($post) {
                      $freshPost = Post::with(['media', 'categories'])->find($post->id);
          
                      \Log::info('Post created.', [
                          'post_id' => $freshPost->id,
                          'media' => $freshPost->media,
                          'categories' => $freshPost->categories
                      ]);
                  });
                  static::creating(function ($post) {
                      $post->sort_order = static::max('sort_order') + 1;
                  });
              }
          
              protected $fillable = [
                  'title',
                  'body',
                  'status',
                  'sort_order',
                  'author_id',
                  'mp3',
                  'created_at',
                  'updated_at',
              ];
          
              protected $casts = [
                  'id' => 'integer',
                  'status' => 'boolean',
                  'sort_order' => 'integer',
                  'author_id' => 'integer',
                  'created_at' => 'datetime',
                  'updated_at' => 'datetime',
              ];
          
              public function author(): BelongsTo
              {
                  return $this->belongsTo(Author::class, 'author_id');
              }
          
              public function getFormattedCreatedAtAttribute()
              {
                  Carbon::setLocale('tr'); // Türkçe locale ayarı
          
                  return $this->created_at->translatedFormat('d F Y'); // Örneğin: 15 Haziran 2018
              }
          
              public function getPostUrl(): string
              {
                  return url('/haber/'.Str::slug($this->title).'/'.$this->id);
              }
          
              public function getPostImage(): ?string
              {
                  $mediaItem = $this->getFirstMedia('post');
          
                  return $mediaItem ? $mediaItem->getUrl() : null;
              }
          
              public function categories()
              {
                  return $this->belongsToMany(
                      Category::class,
                      'category_post',
                      'post_id',
                      'category_id'
                  );
              }
          
              public function tags()
              {
                  return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');
              }
          }
          class Category extends Model
          {
              use HasFactory;
          
              protected static function boot()
              {
                  parent::boot();
          
                  static::creating(function ($category) {
                      $category->sort_order = static::max('sort_order') + 1;
                  });
              }
          
              protected $fillable = [
                  'name',
                  'sort_order',
                  'status',
              ];
          
              protected $casts = [
                  'id' => 'integer',
                  'sort_order' => 'integer',
                  'status' => 'boolean',
              ];
          
              public function posts()
              {
                  return $this->belongsToMany(
                      Post::class,
                      'category_post',
                      'category_id',
                      'post_id'
                  );
              }
          }

            macintyre Yani yeni bir Post'u ve ilişkileri oluşturduğunuz yeri görmek istedim. Post::create() yaptığınız yeri.

              mgsmus Filamentphp kullanıyorum resource tetikliyor

              <?php
              
              namespace App\Filament\Resources;
              
              use App\Filament\Resources\PostResource\Pages;
              use App\Models\Category;
              use App\Models\Post;
              use App\Models\Tag;
              use Filament\Forms;
              use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
              use Filament\Forms\Form;
              use Filament\Resources\Resource;
              use Filament\Tables;
              use Filament\Tables\Table;
              
              /**
               * @property \App\Models\Post $record
               */
              class PostResource extends Resource
              {
                  protected static ?string $model = Post::class;
              
                  protected static ?string $navigationIcon = 'gmdi-post-add-r';
              
                  protected static ?string $navigationLabel = 'Haberler';
              
                  protected static ?string $pluralLabel = 'Haberler';
              
                  public static function form(Form $form): Form
                  {
                      return $form
                          ->columns(4)
                          ->schema([
                              Forms\Components\Section::make([
                                  Forms\Components\TextInput::make('title')
                                      ->required()
                                      ->columnSpan(2)
                                      ->maxLength(255),
                                  Forms\Components\Select::make('author_id')
                                      ->relationship('author', 'name')
                                      ->default(1)
                                      ->columnSpan(2)
                                      ->required(),
                              ])->columns(4),
                              Forms\Components\Section::make([
                                  Forms\Components\Select::make('categories')
                                      ->multiple()
                                      ->relationship('categories', 'name')
                                      ->options(Category::pluck('name', 'id'))
                                      ->searchable()
                                      ->required(),
                                  Forms\Components\Select::make('tag')
                                      ->multiple()
                                      ->relationship('tags', 'name')
                                      ->options(Tag::pluck('name', 'id'))
                                      ->createOptionForm([
                                          Forms\Components\TextInput::make('name')
                                              ->required(),
                                      ])
                                      ->preload() // Optionally preload existing tags for faster selection
                                      ->searchable() // Allow searching tags
                                      ->required(),
                                  Forms\Components\DatePicker::make('created_at')
                                      ->native(false)
                                      ->displayFormat('d F Y')
                                      ->required(),
                              ])->columns(3),
              
                              SpatieMediaLibraryFileUpload::make('post')->label('Baş Resim')
                                  ->image()
                                  ->columnSpanFull()
                                  ->imageEditor()
                                  ->collection('post')
                                  ->disk('custom_public'), // Bu satırı ekledik
                              Forms\Components\FileUpload::make('mp3')
                                  ->disk('custom_public')
                                  ->acceptedFileTypes(['audio/mpeg']),
                              Forms\Components\RichEditor::make('body')
                                  ->required()
                                  ->fileAttachmentsDisk('custom_public')
                                  ->columnSpanFull(),
                          ]);
                  }
              
                  public static function table(Table $table): Table
                  {
                      return $table
                          ->columns([
                              Tables\Columns\TextColumn::make('title')
                                  ->searchable(),
                              Tables\Columns\IconColumn::make('status')
                                  ->boolean(),
                              Tables\Columns\TextColumn::make('sort_order')
                                  ->numeric()
                                  ->sortable(),
                              Tables\Columns\TextColumn::make('author.name')
                                  ->numeric()
                                  ->sortable(),
                              Tables\Columns\TextColumn::make('created_at')
                                  ->dateTime()
                                  ->sortable()
                                  ->toggleable(isToggledHiddenByDefault: true),
                              Tables\Columns\TextColumn::make('updated_at')
                                  ->dateTime()
                                  ->sortable()
                                  ->toggleable(isToggledHiddenByDefault: true),
                          ])
                          ->filters([
                              //
                          ])
                          ->actions([
                              Tables\Actions\ViewAction::make(),
                              Tables\Actions\EditAction::make(),
              
                          ])
                          ->bulkActions([
                              Tables\Actions\BulkActionGroup::make([
                                  Tables\Actions\DeleteBulkAction::make(),
                              ]),
                          ]);
                  }
              
                  public static function getRelations(): array
                  {
                      return [
                          //
                      ];
                  }
              
                  public static function getPages(): array
                  {
                      return [
                          'index' => Pages\ListPosts::route('/'),
                          'create' => Pages\CreatePost::route('/create'),
                          'view' => Pages\ViewPost::route('/{record}'),
                          'edit' => Pages\EditPost::route('/{record}/edit'),
                      ];
                  }
              }

                macintyre Anladım sanırım, sizinle ilgili değil. saved/created olayı model kaydedildikten sonra tetikleniyor. İlişkiler için ise model id gerektiği için sonradan oluşturuluyor. O yüzden saved sırasında sadece modeli dinleyebiliyorsunuz. İlişki saved olayından sonra gerçekleşiyor. Yani işlem sırası

                • Post
                • Post creating/saving
                • Post created/saved
                • Post retrieved (Eğer Post modeline erişmek için tekrar sorgu yapılıyorsa)
                • Category
                • Category creating/saving
                • Category created/saved

                şeklinde gerçekleşiyor. Bunun için model event değil normal event/listener kurgusu kullanmanız lazım. Filamentphp kullanmadığım için onda nasıl yapılır bilmiyorum.

                  Filament kullanıyorsanız filament'in Hooklarını kullansanız daha sağlıklı olur ve yakalanması daha kolay olur diye düşünüyorum.

                  https://filamentphp.com/docs/2.x/admin/resources/editing-records#lifecycle-hooks

                  Örnek olarak

                  protected function beforeSave(): void
                  {
                      if (! $this->record->team->subscribed()) {
                          Notification::make()
                              ->warning()
                              ->title('You don\'t have an active subscription!')
                              ->body('Choose a plan to continue.')
                              ->persistent()
                              ->actions([
                                  Action::make('subscribe')
                                      ->button()
                                      ->url(route('subscribe'), shouldOpenInNewTab: true),
                              ])
                              ->send();
                      
                          $this->halt();
                      }
                  }

                    koti42 Teşekkür ederim, ilgilendiğiniz için

                    https://filamentphp.com/docs/3.x/actions/prebuilt-actions/create#lifecycle-hooks

                    1. sürümdeki şu dökümanı kullandım ve işe yaramadı log dahi tetiklenmiyor. ilginç
                    class ListPosts extends ListRecords
                    {
                        protected static string $resource = PostResource::class;
                    
                        protected function getHeaderActions(): array
                        {
                            return [
                                Actions\CreateAction::make()
                                    ->after(function (Actions\CreateAction $action, Post $record) {
                                        Log::info("CreateAction after fn started");
                                        event(new CachePostsEvent());
                                        Log::info("CreateAction after fn completed");
                                    }),
                            ];
                        }
                    }

                    mgsmus Bunun için model event değil normal event/listener kurgusu kullanmanız lazım.

                    Mustafa Bey'in tavsiyesi ile event-listener ikilisi oluşturdum ve bunu Post modelin saved olayında tetikledim.

                    Post Model

                    static::saved(function ($post) {
                        try {
                            $i = 1;
                            while (Cache::has("posts-page-$i")) {
                                Cache::forget("posts-page-$i");
                                $i++;
                            }
                            Log::info("Pages Cache cleared");
                            event(new CachePostsEvent(postId: $post->id));
                        } catch (\Throwable $th) {
                            Log::error('Error clearing cache for posts-page-: ' . $th->getMessage(), ['exception' => $th]);
                        }
                    });

                    Listener

                        public function handle(CachePostsEvent $event): void
                        {
                            try {
                                $post_id = $event->postId;
                    
                                $post = Post::with('categories', 'media')->find($post_id);
                                Log::info("Post ", ["Post" => $post]);
                            } catch (\Throwable $th) {
                                Log::error("Error sending GET requests to all pages: " . $th->getMessage(), [
                                    "exception" => $th
                                ]);
                            }
                        }

                    Bu sayede hedefime ulaştım çok çok teşekkürler.

                    9 gün sonra

                    Selam, bir diğer yöntem Observer da çözülebilir. Genelde ben bu şekilde kullanıyorum.

                      18 gün sonra

                      Funal Selam, Observer sınıfı olay ve dinleyici yapısını tek bir dosya içerisine topluyor. Depo (Repository) tasarım kalıbı gibi. Çalıştırdığı olay boot() içerisindeki static:: ile başlayan olaylar yine, bu yüzden bir farklılık yaratmıyor.

                      https://laravel.com/docs/12.x/eloquent#observers

                      Observers
                      Defining Observers

                      If you are listening for many events on a given model, you may use observers to group all of your listeners into a single class.