Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?

Serhatt Neden ihtiyaçlarınıza göre kendiniz geliştirmiyorsunuz? Abonelik SaaS ürünlerde önemli bir bölüm ve kontrolünün tamamen sizde olması daha iyi olur.

    mgsmus
    kontrol saglamam gereken alanları ve değişkenler için bir türlü mantıgı oturtamadım malesef
    biraz mantıgı acayım sizlere belki mantıken de yardımınız olur

    sistemimde bulunanlar
    users, domains, bots ve categories, posts tabloları
    kullanıcılar domainlere ve botlara abone olacaklar bir kullanıcını aboneligi 1 domain içeriyorsa 1 domainle kısıtlı kalacak
    birden fazla domain eklemeyi içeren bir abonelige sahipse aboneliginin içerdiği kadar domain ekleyebilecek örnegin 5-10 domain gibi
    benzer kosullar bots tablosu için de geçerli bots tablosuna üyeler ekleme yapmayacak sadece burda bulunan aboneligini kapsayan botlara ait postları görüntüleyebilecek
    örnegin 3 adet bot aboneligi var o 3 botun içeriklerini görüntüleyebilecek
    yada tüm botlara erişimi var tüm botlara ait postları görüntüleyebilecek
    aynı mantık yine kategoriler içinde geçerli örnegin üye sadece 2 kategori den içerik almak istiyor diger kategorileri istemiyor
    bot farketmeksizin o iki kategori içeriklerini alabilmeli
    yada 5 bota ait 3 kategori gibi değişik varyasyonlar mevcut olacak

      Serhatt

      plans
      +----+-------+
      | id | name  |
      +----+-------+
      |  1 | Basic |
      |  2 | Pro   |
      +----+-------+
      subscriptions
      +----+---------+---------+---------------------+---------------------+---------------+
      | id | user_id | plan_id |      starts_at      |       ends_at       | trial_ends_at |
      +----+---------+---------+---------------------+---------------------+---------------+
      |  1 |       1 |       1 | 2022-11-20 23:40:56 | 2022-12-20 23:40:56 |               |
      +----+---------+---------+---------------------+---------------------+---------------+
      features
      +----+----------+-----------------------+
      | id |   slug   |         name          |
      +----+----------+-----------------------+
      |  1 | domain   | Domain Subscription   |
      |  2 | bot      | Bot Subscription      |
      |  3 | category | Category Subscription |
      +----+----------+-----------------------+
      plan_feature
      +----+---------+------------+----------+-------+--------+
      | id | plan_id | feature_id | pricing  | price | limits |
      +----+---------+------------+----------+-------+--------+
      |  1 |       1 |          1 | per_subs |  4.95 |      3 |
      +----+---------+------------+----------+-------+--------+
      planables
      +---------+------------+-----------------+-------------+-------------------+
      | plan_id | feature_id | subscription_id | planable_id |   planable_type   |
      +---------+------------+-----------------+-------------+-------------------+
      |       1 |          1 |               1 |           1 | App\Models\Domain |
      +---------+------------+-----------------+-------------+-------------------+

      ---

      User

      public function subscriptions(): HasMany
      {
          return $this->hasMany(Subscription::class);
      }
      
      public function currentSubscription(): HasOne
      {
          return $this->hasOne(Subscription::class)
              ->latestOfMany();
      }

      Subscription

      public function user(): BelongsTo
      {
          return $this->belongsTo(User::class);
      }
      
      public function plan(): BelongsTo
      {
          return $this->belongsTo(Plan::class);
      }
      
      public function domains(): MorphedByMany
      {
          return $this->morphedByMany(Domain::class, 'planable');
      }
      
      public function bots(): MorphedByMany
      {
          return $this->morphedByMany(Bot::class, 'planable');
      }
      
      public function categories(): MorphedByMany
      {
          return $this->morphedByMany(Category::class, 'planable');
      }
      
      public function isTrial(): bool
      {
          return !is_null($this->trial_ends_at);
      }
      
      public function hasExpired(): bool
      {
          if($this->isTrial()) {
              return $this->trial_ends_at
                  ->isPast();
          }
          
          return $this->ends_at
              ->isPast();
      }
      
      public function expiresIn(string $interval = 'days'): int
      {
          if($this->hasExpired()) {
              return 0;
          }
      
          $endsAt = $this->isTrial()
              ? $this->trial_ends_at
              : $this->ends_at;
      
          return match($interval) {
              'days' => $endsAt->diffInDays(),
              'hours' => $endsAt->diffInHours(),
          };
      }

      Plan

      public function subscriptions(): HasMany
      {
          return $this->hasMany(Subscription::class);
      }
      
      public function features(): BelongsToMany
      {
          return $this->belongsToMany(Feature::class, 'plan_feature', 'plan_id', 'feature_id')
              ->withPivot('pricing', 'price', 'limits');
      }
      
      public function domains(): MorphedByMany
      {
          return $this->morphedByMany(Domain::class, 'planable');
      }
      
      public function bots(): MorphedByMany
      {
          return $this->morphedByMany(Bot::class, 'planable');
      }
      
      public function categories(): MorphedByMany
      {
          return $this->morphedByMany(Category::class, 'planable');
      }

      Domain, Bot, Category

      public function plans(): MorphToMany
      {
          return $this->morphToMany(Plan::class, 'planable');
      }

      ---

      $now = now()->toImmutable();
      
      $user = User::findOrFail(1);
      
      $plan = Plan::findOrFail(1); // Basic
      
      // Kullanıcı Basic plana şimdiden başlayıp 1 ay boyunca abone oluyor
      $subscription = $user->subscriptions()
          ->create([
              'plan_id' => $plan->id,
              'starts_at' => $now,
              'ends_at' => $now->addMonth()
          ]);
      // Mevcut abonelik
      $currentSubscription = $user->currentSubscription;
      
      // Mevcut aboneliğin süresi doldu mu?
      $currentSubscription->hasExpired();
      
      // Mevcut abonelik deneme mi?
      $currentSubscription->isTrial();
      
      // Mevcut abonelik kaç gün/saat içinde bitiyor?
      $currentSubscription->expiresIn(); // ->expiresIn('hours')
      
      // Mevcut abone olunan plan
      $plan = $currentSubscription->plan;
      
      // Plan özellikleri
      $features = $plan->features;
      
      foreach($features as $feature) {
          $feature->pricing; // Fiyatlandırma şekli. Kullanım sayısına göre vs
          $feature->price; // Fiyatı
          $feature->limits; // Kullanım limiti
          $feature->feature->name; // "Domain Aboneliği"
          $feature->feature->slug; // "domain"
      }
      
      // Kullanıcının abone olduğu planda domain özelliği var mı?
      $features->contains('domain');
      
      // Plan dahilinde kullanıcının abone olduğu domainler, botlar ve kategorier
      $domains = $currentSubscription->where('plan_id', $plan->id)
          ->domains()
          ->get();
          
      $bots = $currentSubscription->where('plan_id', $plan->id)
          ->bots()
          ->get();
          
      $categories = $currentSubscription->where('plan_id', $plan->id)
          ->categories()
          ->get();
      
      // Kullanıcı mevcut aboneliğin planında example.com domainine abone olmuş mu?
      $domains->contains('name', 'example.com');
      
      // Kullanıcı mevcut aboneliğin planında hangi kategori id'lerini görebilir
      $categories->pluck('id');

      şeklinde gidiyor... Daha yazılacak çok kod var ama benden bu kadar, gerisi sizin.

      https://laravel.com/docs/9.x/eloquent-relationships
      https://laravel.com/docs/9.x/collections#method-contains

        @mgsmus detaylı yapı için cok teşekkürler başlangıç için bana yeterli ilerleyen zamanlarda takıldıgım yer olursa sorarım yine insallah

        @mgsmus
        abi yeniden selamlar
        plans
        features
        plan_feature
        bu üç tablo arasındaki ilişkide bir problem mi var acaba
        Planlarım

        Özelliklerim

        Plan Özellik İlişki Tablom

        Adımlarım
        Plan Ekle (plan adı,plan süresi) kaydet
        Özellik Ekle

        kaydederken kodum su sekilde
        $feature = Feature::create($request->only('name'));
        $plan = Plan::find($request->plan_id);
        $plan->features()->attach([
        [
        'feature_id' => $feature->id,
        'price' => $request->price ?? 0,
        'limits' => $request->limits,
        ]
        ]);

        burada plana sorgu atıp plan üzerinden özelliği kaydetmem gerekiyor
        düzenlerken plan_feature tablosundaki verileri alamıyorum
        Feature üzerinden de bir ilişki mi kurulması lazım plan_feature deki verileri almak için
        ve aynı sekilde Feature den Plan ı almak için

        Ben yanlıs mantıkta ilerliyormusum şuan için bu bölüm tamam
        planları ve özellikleri bagımsız ekliyorum
        plan düzenleme bölümünde plana ait özellikleri listeleyip
        eklemek istedigim özellikleri multi select içinden seçip planı kaydediyorum
        $plan->features()->sync($request->features);
        seçili olanları direk plana dahil ediyor yada cıkarıyor
        plan düzenlemenin altında bir tablo koydum tabloda da
        fiyat ve limite güncelleme attırıyorum

        mgsmus
        abi selamlar
        yeni konus acmak istemedim direk aynı konu üzerinde devam edeyim dedim

        planables tablosu ve ilişkileri problemlimi abi yukarıdaki anlatımında yoksa benim bakıs acımmı yanlıs

        ilk olarak features tablomuza özelliklerimizi herseyden bagımsız olarak ekliyoruz
        ardından plans tablomuza planlarımızı ekliyoruz
        $plan = Plan::create($request->only('name', 'interval'));
        $plan->features()->attach($request->features);

        plan adı süresi ve multiselect ile seçtigim özellikler plana dahil oluyor
        plan düzenle bölümümde plana ait özelliklerin fiyatlarını ve limitlerini düzenliyorum
        bu adımlarda problem yok
        ardından bir kullanıcıya abonelik ekliyorum
        abonelik ekleme de
        user_id,plan_id,trial_ends_at bots ve categories alanlarımı gönderiyorum formdan
        controller tarafında kodum su sekilde
        $now = now()->toImmutable();
        $plan = Plan::find($request->plan_id);
        $subscription = new Subscription();
        $subscription->user_id = $request->user_id;
        $subscription->plan_id = $request->plan_id;
        $subscription->starts_at = $now;
        $subscription->ends_at = $now->addMonth($plan->interval);
        $subscription->trial_ends_at = $request->trial_ends_at;
        $subscription->save();
        if ($plan->features->contains('id', 5)) {
        $subscription->bots()->attach(Bot::all()->pluck('id'));
        }else{
        $subscription->bots()->attach($request->bots);
        }
        if ($plan->features->contains('id', 6)) {
        $subscription->categories()->attach(Category::all()->pluck('id'));
        }else{
        $subscription->categories()->attach($request->categories);
        }

        burda yapmak istedigim formdan gelen planın
        özelliklerinde tüm botlar varsa tüm botları abonelige eklemek
        özelliklerinde tüm kategoriler varsa tüm kategorileri abonelige eklemek
        formdan veriyi gönderdigimde
        feature_id ve plan_id sütunları boş geliyor diye hata alıyorum
        bu tablo da sadece aboneligin kullanacagı botlar kategoriler ve domainler olmayacakmı
        feature_id ve plan_id nin bu tablodaki amacını anlayamadım
        bu tablo subscribable isminde
        subscription_id,subscribable_id,subscribable_type
        sütunlarından olusması gerekmiyormu
        burda benim kacırdıgım bir mantık vs mi var acaba

        abonelige baglı domainleri kullanıcı kendisi ekleyecek kaç domain ekleme limiti varsa ona göre

          Serhatt Siz plan oluştururken, plana örneğin 3'lü paket diye bir bot paketi ekleyeceğinizde bu 3 botu siz mi seçip botları ön tanımlı bir plan oluşturacaksınız yoksa sadece 3 bot seçileceğini belirtip kullanıcının daha sonra 3 tane bot seçmesini mi sağlayacaksınız?

            üstteki sql yapımda sürelere göre paket belirlemesi yapmıstım bu kullanımımında yanlıs oldugunu anladım
            böyle kullanırsam aboneligin özelliklerini listeledigimde
            aynı özellik birden fazla dönmüş oluyor o yapıyı düzenledim suan
            sizin belirttiginiz gibi
            Basic
            Premium
            Enterprise
            planları ekledim
            Özelliklerde Domain, Kategori, Bot Standart Kalıyor
            Basic te 1 Domain
            premiumda 5 Domain
            Enterprise da Domain özelliğinin limit alanı null geçiyorum
            başlangıç ve bitiş süreleri abonelik tablomda zaten var
            satın alırken kac aylık seçtiyse müşteri seçtigi ay*özellikler ücreti toplamı diyecegim
            sadece planable tablo yapısı sorunlu gibi duruyor suanda

              Serhatt Şöyle bir kod yazmışsınız:

              $subscription->bots()->attach($request->bots); 
              // Bu arada daima $request->input('bots') şeklinde kullanın,
              // $request->bots şeklinde kullanmayın

              Bu kodun planables tablosuna nasıl kayıt eklediğini görsel olarak anlatmaya çalışayım. $request->input('bots') değeri [3,5,9] olmak üzere;

              planables
              +---------+------------+-----------------+----------------+-------------------+
              | plan_id | feature_id | subscription_id | planable_type  |    planable_id    |
              +---------+------------+-----------------+----------------+-------------------+
              | null    | null       | 1               | App\Models\Bot | 3                 |
              | null    | null       | 1               | App\Models\Bot | 5                 |
              | null    | null       | 1               | App\Models\Bot | 9                 |
              +---------+------------+-----------------+----------------+-------------------+
              | -       | -          | $subscription   | ->bots()       | ->attach([3,5,9]) |
              +---------+------------+-----------------+----------------+-------------------+

              En alttaki satırda hangi kod segmenti hangi alanı dolduruyor onu gösteriyor.

              • Ana model olarak $subscription kullanınca subscription_id doluyor.
              • bots isimli MorphedByMany ilişkisini kullanınca planable_type alanı doluyor.
              • attach([1,2,3]) yaparak da her bir planable_type için planable_id satırı oluşuyor.

              Burada yaptığınız hata ise, plan_id ve feature_id vermiyorsunuz. Yazdığınız koda ve verdiğim tabloya vs bakarsanız içinde hiç plan ve feature ile ilgili bir şeyler olmadığını zaten görüyorsunuz. Peki nasıl olması gerekiyordu? Dokümanda da belirttiği gibi:
              https://laravel.com/docs/9.x/eloquent-relationships#updating-many-to-many-relationships

              ilişki alanları haricindeki alanları her bir planable_id için de ekstra alan olarak vermeniz gerekiyor. Yani açık veri şeklinde ifade etmek gerekirse şunu elde etmelisiniz:

              $subscription                  // subscription_id alanını doldur
                  ->bots()                   // planable_type alanını doldur
                  ->attach([
                      3 => [                 // planable_id alanını doldur
                          'plan_id' => 1,    // plan_id alanını doldur
                          'feature_id' => 1  // feature_id alanını doldur
                      ],
                      5 => [
                          'plan_id' => 1,
                          'feature_id' => 1
                      ],
                      9 => [
                          'plan_id' => 1,
                          'feature_id' => 1
                      ]
                  ]); 
              bir yıl sonra

              benim için en temel seviyede plans ve subscriptions yeterli işime yararmı bilemedim ama belki plan_feature tablosuda da olabilir .
              Kullanıcılar Free , Basic , Premium planlarından birini secer yıllık ödeme yada aylık ödeme vs tüm kullanıcılar default olarak free dir ancak bazı rotalar ve adresleri ve bazı işlemleri aboneliğe göre yapabiliyo olması gerek örneğin bir rotaya girememesi yada bir formu bellli sayıda oluşturabilmesi gibi işlemler için middlware mı yazmalıyım yada controllerda bu planını mı kontrol edicem