Depo düzenine bağlı kalarak Laravel'de ilişkileri yönetme


120

Laravel 4'te T.Otwell'in Laravel'de iyi tasarım desenleri hakkındaki kitabını okuduktan sonra bir uygulama oluştururken kendimi uygulamadaki her tablo için havuzlar oluştururken buldum.

Aşağıdaki tablo yapısını elde ettim:

  • Öğrenciler: kimlik, ad
  • Dersler: id, name, teacher_id
  • Öğretmenler: id, isim
  • Atamalar: id, name, course_id
  • Puanlar (öğrenciler ve ödevler arasında bir pivot görevi görür): öğrenci_kimliği, atama_kimliği, puanlar

Tüm bu tablolar için find, create, update ve delete yöntemlerine sahip depo sınıflarım var. Her deponun, veritabanıyla etkileşime giren bir Eloquent modeli vardır. İlişkiler, Laravel'in belgelerine göre modelde tanımlanmıştır: http://laravel.com/docs/eloquent#relationships .

Yeni bir kurs oluştururken, tek yaptığım Kurs Havuzunda oluşturma yöntemini çağırmak. Bu kursta ödevler var, bu yüzden bir tane oluştururken, dersteki her öğrenci için puan tablosunda da bir giriş oluşturmak istiyorum. Bunu Atama Havuzu aracılığıyla yapıyorum. Bu, ödev havuzunun, Ödev ve Öğrenci modeli ile iki Eloquent modeli ile iletişim kurduğu anlamına gelir.

Sorum şu: Bu uygulama muhtemelen boyut olarak büyüyecek ve daha fazla ilişki tanıtılacağı için, depolarda farklı Eloquent modelleriyle iletişim kurmak iyi bir uygulama mı yoksa bunun yerine başka havuzlar kullanılarak mı yapılmalı (Atama havuzundan diğer depoları çağırmaktan bahsediyorum ) yoksa Eloquent modellerinde hep birlikte mi yapılmalıdır?

Ayrıca, puan tablosunu ödevler ve öğrenciler arasında bir pivot olarak kullanmak iyi bir uygulama mı yoksa başka bir yerde mi yapılmalı?

Yanıtlar:


71

Fikir sorduğunuzu unutmayın: D

Benimki burada:

TL; DR: Evet, sorun değil.

İyi gidiyorsun!

Sık sık yaptığınız şeyi tam olarak yapıyorum ve harika çalıştığını düşünüyorum.

Bununla birlikte, çoğu zaman, tablo başına depo yerine iş mantığı etrafında depoları organize ederim. Bu, uygulamanızın "iş sorununuzu" nasıl çözmesi gerektiğine odaklanan bir bakış açısı olduğu için yararlıdır.

Kurs, özniteliklere (başlık, kimlik vb.) Ve hatta diğer varlıklara (kendi özniteliklerine ve muhtemelen varlıklara sahip olan Ödevler) sahip bir "varlık" tır.

"Kurs" havuzunuz bir Kursu ve Kursların niteliklerini / Ödevlerini (Ödev dahil) döndürebilmelidir.

Neyse ki, Eloquent ile bunu başarabilirsiniz.

(Genellikle tablo başına bir havuzla karşılaşırım, ancak bazı havuzlar diğerlerinden çok daha fazla kullanılır ve bu yüzden çok daha fazla yöntemi vardır. "Kurslar" havuzunuz, Ödevler havuzunuzdan çok daha tam özellikli olabilir, örneğin, uygulama daha çok Kurslar etrafında odaklanır ve Kursların Ödevler koleksiyonundan daha azdır).

Zor kısım

Bazı veritabanı eylemlerini gerçekleştirmek için genellikle depolarımın içinde depoları kullanırım.

Verileri işlemek için Eloquent'i uygulayan herhangi bir depo büyük olasılıkla Eloquent modellerini döndürecektir. Bu açıdan bakıldığında, Kurs modelinizin Ödevleri (veya başka herhangi bir kullanım senaryosunu) almak veya kaydetmek için yerleşik ilişkiler kullanması iyi olur. "Uygulamamız", Eloquent temel alınarak oluşturulmuştur.

Pratik bir bakış açısından, bu mantıklı. Veri kaynaklarını, Eloquent'in işleyemeyeceği bir şeye (sql olmayan bir veri kaynağına) değiştirme olasılığımız düşüktür.

ORMS'nin

Bu düzenin en zor kısmı, en azından benim için, Eloquent'in bize gerçekten yardım edip etmediğini belirlemektir. ORM'ler zor bir konudur, çünkü bize pratik bir bakış açısından büyük ölçüde yardımcı olurken, aynı zamanda "iş mantığı varlıkları" kodunuzu veri alımını yapan kodla birleştirirler.

Bu tür, deponuzun sorumluluğunun gerçekten veri işleme mi yoksa varlıkların (iş etki alanı varlıkları) alınması / güncellenmesi için mi olduğu konusunda karışıklık yaratır.

Dahası, görüşlerinize aktardığınız nesneler olarak hareket ederler. Daha sonra bir havuzda Eloquent modellerini kullanmaktan uzaklaşmanız gerekirse, görünümlerinize aktarılan değişkenlerin aynı şekilde davrandığından veya aynı yöntemlerin mevcut olduğundan emin olmanız gerekir, aksi takdirde veri kaynaklarınızı değiştirmek, görüş ve siz (kısmen) mantığınızı depolara soyutlama amacını kaybettiniz - projenizin sürdürülebilirliği.

Her neyse, bunlar biraz eksik düşünceler. Belirtildiği gibi, sadece benim fikrim, bu da Domain Driven Design'ı okumanın ve geçen yıl Ruby Midwest'te "bob amca'nın" açılış konuşması gibi videoları izlemenin bir sonucu .


1
Sizce depoların anlamlı nesneler yerine veri aktarım nesneleri döndürmesi iyi bir alternatif olur mu? Elbette bu, belagatandan dto'lara fazladan bir dönüşüm anlamına gelir, ancak bu şekilde, en azından denetleyicilerinizi / görünümlerinizi mevcut orm uygulamasından ayırırsınız.
federivo

1
Bunu kendim de biraz denedim ve biraz pratik olmayan tarafta buldum. Bununla birlikte, soyuttaki bu fikir hoşuma gitti. Bununla birlikte, Illuminate'in veritabanı Koleksiyon nesneleri tıpkı diziler gibi davranır ve Model nesneleri, yeterince StdClass nesneleri gibi davranır, böylece pratik olarak konuşursak, Eloquent'e bağlı kalabiliriz ve gelecekte ihtiyaç duyduğumuzda yine de dizileri / nesneleri kullanabiliriz.
fideloper

4
@fideloper Depoları kullanırsam, Eloquent'in sağladığı ORM'nin tüm güzelliğini kaybedeceğimi hissediyorum. Benim depo yöntemi ile bir hesap nesne alma zaman $a = $this->account->getById(1)ben sadece gibi yöntemleri zinciri olamaz $a->getActiveUsers(). Tamam, kullanabilirim $a->users->..., ama sonra bir Eloquent koleksiyonunu iade ediyorum ve stdClass nesnesi yok ve tekrar Eloquent'e bağlıyım. Bunun çözümü nedir? Gibi kullanıcı havuzunda başka bir yöntem bildirmek $user->getActiveUsersByAccount($a->id);? Bunu nasıl
çözdüğünüzü

1
ORM'ler Kurumsal (ish) düzeyinde mimari için korkunç çünkü bu tür sorunlara neden oluyorlar. Sonunda, başvurunuz için neyin en mantıklı olduğuna karar vermelisiniz. Kişisel olarak, Eloquent ile depoları kullanırken (zamanın% 90'ında!) Eloquent kullanıyorum ve modelleri ve koleksiyonları stdClasslar ve Diziler gibi ele almak için elimden gelenin en iyisini yapmaya çalışıyorum (çünkü yapabilirsiniz!), Bu yüzden gerekirse, başka bir şeye geçmek mümkün.
destekçi

5
Devam edin ve tembel yüklü modelleri kullanın. Eloquent'i kullanmayı atlarsanız, gerçek etki alanı modellerinin bu şekilde çalışmasını sağlayabilirsiniz. Ama cidden, sen olacak hiç anlamlı dışarı anahtarı? Bir kuruş için, bir pound için! ("Kurallara" bağlı kalmaya çalışarak aşırıya kaçmayın! Ben her zaman benimkini ihlal ederim).
destekleyici

224

Laravel 4 kullanarak büyük bir projeyi bitiriyorum ve şu anda sorduğunuz tüm soruları yanıtlamam gerekiyordu. Leanpub'daki mevcut tüm Laravel kitaplarını ve tonlarca Googling'i okuduktan sonra aşağıdaki yapıyı buldum.

  1. Tarihlenebilir tablo başına bir Eloquent Model sınıfı
  2. Her Eloquent Modeli için bir Depo sınıfı
  3. Birden çok Depo sınıfı arasında iletişim kurabilen bir Hizmet sınıfı.

Diyelim ki bir film veritabanı oluşturuyorum. En azından aşağıdaki Eloquent Model sınıflarına sahip olacaktım:

  • Film
  • Stüdyo
  • yönetmen
  • Aktör
  • gözden geçirmek

Bir depo sınıfı, her bir Eloquent Model sınıfını kapsayacak ve veritabanı üzerindeki CRUD işlemlerinden sorumlu olacaktır. Depo sınıfları şöyle görünebilir:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

Her depo sınıfı, aşağıdaki arabirimi uygulayan bir BaseRepository sınıfını genişletir:

interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}

Bir Service sınıfı, birden çok depoyu birbirine yapıştırmak için kullanılır ve uygulamanın gerçek "iş mantığını" içerir. Denetleyiciler yalnızca Oluşturma, Güncelleme ve Silme eylemleri için Hizmet sınıflarıyla iletişim kurar.

Bu nedenle, veritabanında yeni bir Movie kaydı oluşturmak istediğimde, MovieController sınıfım aşağıdaki yöntemlere sahip olabilir:

public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}

Verileri denetleyicilerinize nasıl POST yapacağınızı belirlemek size kalmıştır, ancak diyelim ki postCreate () yönteminde Input :: all () tarafından döndürülen veriler şuna benzer:

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)

MovieRepository'nin veritabanında Actor, Director veya Studio kayıtlarını nasıl oluşturacağını bilmemesi gerektiğinden, aşağıdaki gibi görünen MovieService sınıfımızı kullanacağız:

public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}

Öyleyse bıraktığımız şey, endişelerin hoş ve mantıklı bir ayrımı. Depolar yalnızca ekledikleri ve veritabanından aldıkları Eloquent modelinin farkındadır. Denetleyiciler depoları önemsemezler, sadece kullanıcıdan topladıkları verileri verirler ve uygun hizmete iletirler. Hizmet, aldığı verilerin veri tabanına nasıl kaydedildiğini umursamaz , sadece denetleyici tarafından verilen ilgili verileri uygun depolara verir.


8
Bu yorum, açık ara daha temiz, daha ölçeklenebilir ve sürdürülebilir bir yaklaşımdır.
Andreas

4
1! Bu bana çok yardımcı olacak, bizimle paylaştığınız için teşekkürler! Hizmetlerdeki şeyleri mümkünse nasıl doğrulamayı başardığınızı merak ederek, ne yaptığınızı kısaca açıklayabilir misiniz? Yine de teşekkürler! :)
Paulo Freitas

6
@PauloFreitas'ın dediği gibi, doğrulama kısmını nasıl ele aldığınızı görmek ilginç olurdu ve istisnalar kısmıyla da ilgilenirim (istisna mı, olaylar mı kullanıyorsunuz yoksa sadece bunu sizin önerdiğiniz gibi hallediyorsunuz) servislerinizde boole dönüşü aracılığıyla kontrolör?). Teşekkürler!
Nicolas

11
İyi bir şekilde yazın, ancak neden denetleyicinin doğrudan depoyla hiçbir şey yapmaması gerektiğinden MovieController'a movieRepository'yi enjekte ettiğinizden emin değilim, ne de movieRepository'yi kullanan postCreate yönteminiz, bu yüzden onu yanlışlıkla bıraktığınızı varsayıyorum ?
davidnknight

15
Bununla ilgili soru: Bu örnekte neden depoları kullanıyorsunuz? Bu dürüst bir soru - bana, depoları kullanıyormuşsunuz gibi görünüyor ama en azından bu örnekte depo, Eloquent ile aynı arayüzü sağlamaktan başka hiçbir şey yapmıyor ve sonunda hala Eloquent'e bağlısınız çünkü hizmet sınıfınız doğrudan it ( $studio->movies()->associate($movie);) içinde eloquent kullanıyor .
Kevin Mitchell

5

Bunu "doğru veya yanlış" yerine kodumun ne yaptığı ve neyden sorumlu olduğu açısından düşünmeyi seviyorum. Sorumluluklarımı bu şekilde ayırıyorum:

  • Denetleyiciler, HTTP katmanıdır ve istekleri temeldeki API'ye yönlendirir (diğer bir deyişle, akışı kontrol eder)
  • Modeller veritabanı şemasını temsil eder ve uygulamaya verilerin neye benzediğini, hangi ilişkilere sahip olabileceğini ve gerekli olabilecek tüm genel nitelikleri (birleştirilmiş bir ad ve soyadı döndürmek için bir ad yöntemi gibi) söyler.
  • Depolar, modellerle daha karmaşık sorguları ve etkileşimleri temsil eder (model yöntemlerle ilgili herhangi bir sorgu yapmıyorum).
  • Arama motorları - karmaşık arama sorguları oluşturmama yardımcı olan sınıflar.

Bunu akılda tutarak, bir depo kullanmak her seferinde mantıklıdır (arayüzler oluşturup oluşturmamanız, vb. Tamamen başka bir konudur). Bu yaklaşımı seviyorum, çünkü bu, belirli bir işi yapmam gerektiğinde tam olarak nereye gideceğimi bildiğim anlamına geliyor.

Ayrıca bir temel depo oluşturma eğilimindeyim, genellikle ana varsayılanları tanımlayan soyut bir sınıf - temelde CRUD işlemleri ve daha sonra her çocuk, gerektiği gibi yöntemleri genişletip ekleyebilir veya varsayılanları aşırı yükleyebilir. Modelinizi enjekte etmek, bu modelin oldukça sağlam olmasına da yardımcı olur.


BaseRepository'nizin uygulamanızı gösterebilir misiniz? Aslında bunu ben de yapıyorum ve ne yaptığını merak ediyorum.
Odyssee

GetById, getByName, getByTitle gibi yöntemler kaydedin. - genellikle çeşitli alanlardaki tüm depolara uygulanan yöntemler.
Oddman

5

Depoları, verilerinizin tutarlı bir dosyalama dolabı olarak düşünün (yalnızca ORM'leriniz değil). Buradaki fikir, verileri tutarlı ve kullanımı kolay bir API'de almak istemenizdir.

Kendinizi sadece Model :: all (), Model :: find (), Model :: create () yaparken bulursanız, bir depoyu soyutlamaktan muhtemelen pek bir fayda sağlamayacaksınız. Öte yandan, sorgularınıza veya eylemlerinize biraz daha fazla iş mantığı yapmak istiyorsanız, verilerle ilgilenmek için API kullanımını daha kolay hale getirmek için bir depo oluşturmak isteyebilirsiniz.

Sanırım, ilgili modelleri birbirine bağlamak için gerekli olan daha ayrıntılı sözdizimlerinin bazılarıyla başa çıkmanın en iyi yolu bir deponun olup olmadığını soruyordunuz. Duruma bağlı olarak yapabileceğim birkaç şey var:

  1. Bir üst model kapalı yeni bir çocuk modeli Asma (tek tek veya tek sayıda), ben gibi çocuk depo şey için bir yöntem eklersiniz createWithParent($attributes, $parentModelInstance)ve bu sadece eklersiniz $parentModelInstance->idiçine parent_idözellikleri ve çağrı oluşturmak alanında.

  2. Bir çok-çok ilişkisi ekleyerek, aslında modeller üzerinde işlevler yaratıyorum, böylece $ instance-> attachChild ($ childInstance) çalıştırabiliyorum. Bunun her iki tarafta da mevcut öğeleri gerektirdiğini unutmayın.

  3. İlgili modelleri tek seferde oluşturarak, Ağ Geçidi dediğim bir şey yaratıyorum (Fowler'in tanımlarından biraz farklı olabilir). Bu şekilde, bir denetleyicide veya komutta sahip olduğum mantığı değiştirebilecek veya karmaşıklaştırabilecek bir grup mantık yerine $ gateway-> createParentAndChild ($ parentAttributes, $ childAttributes) çağırabilirim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.