PHP şablonunda depo şablonu doğru mu?

Önsöz: MVC mimarisinde ilişkisel veritabanlarıyla bir depolama şablonu kullanmaya çalışıyorum.

Yakın zamanda PHP'de TDD öğrenmeye başladım ve veritabanımın başvurumun geri kalanına çok yakından bağlı olduğunu biliyorum. Depoları okudum ve kontrol cihazıma "yerleştirmek" için bir IoC kabı kullandım. Çok güzel şeyler. Ancak şimdi depolama tasarımı hakkında birkaç pratik soru var. Aşağıdaki örneği düşünün.

 <?php class MyController { public function users() { $users = User::select('name, email, status') ->byCountry('Canada')->orderBy('name')->rows(); return View::make('users', array('users' => $users)); } } 

Depoya olan yaklaşımımla, bununla bitmek istemiyorum:

spesifikasyonları hakkında bir şeyler duydum, ancak bana bunun veri tabanından veri alırsanız açıkça ciddi performans problemleri olan tüm kayıt setini ( IsSatisfiedBy() aracılığıyla) IsSatisfiedBy() . 

Bana yardım et

Açıkçası, depolarla çalışırken biraz düşünmem gerekiyor. Birisi bunun en iyi nasıl ele alındığını aydınlatabilir mi?

235
23 апр. Jonathan tarafından 23 Nis tarihinde ayarlandı 2013-04-23 21:50 '13 9: 50'de 2013-04-23 21:50
@ 8 cevaplar

Sorumun cevabını keseceğimi düşündüm. Aşağıdaki, benim ilk sorumdaki 1-3 soruları çözme yollarından sadece bir tanesi.

Feragatname: Desenleri veya yöntemleri açıklarken her zaman doğru koşulları kullanmayabilirim. Bunun için üzgünüm.

Amaç:

  • Users görüntülemek ve düzenlemek için temel bir kontrol cihazı örneği oluşturun.
  • Tüm kodlar tamamen test edilmeli ve prototiplenmelidir.
  • Denetleyicinin verilerin nerede saklandığını bilmesi gerekmez (bu, onu değiştirmek anlamına gelir).
  • Bir SQL uygulamasını görüntüleme örneği (en yaygın).
  • Maksimum performans için, kontrolörler yalnızca gerekli verileri almalıdır - ek alan yoktur.
  • Bir uygulama, gelişimi kolaylaştırmak için bir tür veri dönüştürücü kullanmalıdır.
  • Uygulama karmaşık veri aramaları yapabilmelidir.

karar

Kalıcı depolama (veritabanı) ile etkileşimi iki kategoriye ayırırım : R (okuma) ve CUD (oluşturma, güncelleme, silme). Benim deneyimim, okumanın uygulamayı yavaşlatması. Veri manipülasyonu (CUD) aslında daha yavaş olmasına rağmen, çok daha az sıklıkta olur ve bu nedenle daha az sıkıntı vericidir.

CUD (Oluştur, güncelle, sil) kolaydır. Bu, daha sonra depolama için Repositories aktarılan gerçek modellerle çalışmayı gerektirir. Not: Depolarım hala Okuma yöntemini sunacak, ancak yalnızca bir nesne oluşturmak için gösterilecek değil. Bunun üzerine daha sonra.

R (Oku) kolay değil. Model yok, sadece değerlerin nesnesi var . İsterseniz dizileri kullanın. Bu nesneler, her ne olursa olsun, tek bir model veya birçok modelin karışımı olabilir. Kendilerinde çok ilgi çekici değiller, ama nasıl yaratıldıkları. Query Objects dediğim şeyi kullanıyorum.

kod:

Kullanıcı modeli

Temel kullanıcı modelimizle başlayalım. Lütfen ORM uzantısı veya veritabanı olmadığını unutmayınız. Sadece saf bir ün modeli. Alıcılarınızı ekleyin, ayarlayıcılar, ne olursa olsun kontrol edin.

 class User { public $id; public $first_name; public $last_name; public $gender; public $email; public $password; } 

Havuz arayüzü

Kullanıcı havuzumu oluşturmadan önce kendi depo arayüzümü oluşturmak istiyorum. Bu, depoların denetleyicim tarafından kullanmak için kullanmaları gereken "sözleşmeyi" belirler. Denetleyicimin verilerin nerede saklandığını bilmediğini unutmayın.

Lütfen depolarımda bu yöntemlerden sadece üçünün olacağını unutmayın. save() yöntemi, yalnızca kullanıcı nesnesinin yerleşik bir tanımlayıcı olup olmamasına bağlı olarak, kullanıcıların oluşturulmasından ve güncellenmesinden sorumludur.

 interface UserRepositoryInterface { public function find($id); public function save(User $user); public function remove(User $user); } 

SQL depolama uygulaması

Şimdi arayüz uygulamamı oluşturduğun için. Daha önce de belirtildiği gibi, örneğim bir SQL veritabanından oluşacak. Yinelenen SQL sorguları yazmaktan kaçınmak için veri eşleyici kullanımına dikkat edin.

 class SQLUserRepository implements UserRepositoryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function find($id) { // Find a record with the id = $id // from the 'users' table // and return it as a User object return $this->db->find($id, 'users', 'User'); } public function save(User $user) { // Insert or update the $user // in the 'users' table $this->db->save($user, 'users'); } public function remove(User $user) { // Remove the $user // from the 'users' table $this->db->remove($user, 'users'); } } 

Nesne Arabirimi İste

Şimdi CUD ile (oluştur, güncelle, sil), havuzumuza bakıyoruz, R (Okuma) 'ya odaklanabiliriz. Sorgu nesneleri basitçe bazı arama mantığının kapsüllenmesidir. Bunlar sorgu oluşturucu değildir . Onu depo olarak soyutlayarak, uygulamasını değiştirebilir ve daha kolay test edebiliriz. Bir istek nesnesinin bir örneği AllUsersQuery veya AllActiveUsersQuery , hatta MostCommonUserFirstNames bile MostCommonUserFirstNames .

Belki de “Bu istekler için depolarımda yöntemler oluşturabilir miyim?” Diye düşünüyorsunuz. Evet, ama bu yüzden bunu yapmam.

  • Depolarım, model nesnelerle çalışmak üzere tasarlanmıştır. Gerçek dünyadaki bir uygulamada, tüm kullanıcılarımın bir listesini arıyorsanız neden password alanına ihtiyacım var?
  • Havuzlar genellikle modele özgüdür, ancak istekler genellikle birden fazla model içerir. Peki, yönteminize hangi depoyu koyarsınız?
  • Bu, depolarımı çok basit tutar - şişirilmiş bir yöntem sınıfı değil.
  • Tüm talepler şimdi kendi sınıflarında düzenlenmektedir.
  • Gerçekten de, şu anda havuzlar sadece veri tabanı seviyemi özetlemek için var.

Örneğimde, "AllUsers" kelimesini aramak için bir sorgu nesnesi oluşturacağım. İşte arayüz:

 interface AllUsersQueryInterface { public function fetch($fields); } 

İstek nesnesini uygulamak

Burada, gelişmeyi hızlandırmak için tekrar bir veri görüntüleme cihazı kullanabiliriz. Döndürülen veri kümesinin bir ayarına izin verdiğime dikkat edin - alan. İstediğim kadarıyla, yürütülen sorguyu manipüle etmekle ilgili. Sorgu nesnelerimin sorgu toplayıcı olmadığını unutmayın. Sadece belirli bir sorgu gerçekleştirirler. Ancak, bunu muhtemelen çok fazla kullanacağımı bildiğim için, birkaç farklı durumda kendime alanları belirtme fırsatı veriyorum. İhtiyacım olmayan alanları iade etmek istemiyorum!

 class AllUsersQuery implements AllUsersQueryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function fetch($fields) { return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows(); } } 

Kontrol cihazına gitmeden önce, bunun ne kadar güçlü olduğunu göstermek için başka bir örnek göstermek istiyorum. Belki bir raporlama mekanizmam var ve AllOverdueAccounts için bir rapor oluşturmanız AllOverdueAccounts . Bu benim veri dosyamda zor olabilir ve bu durumda gerçek SQL yazabilirim. Sorun değil, işte bu istek nesnesinin görünüşü:

 class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface { protected $db; public function __construct(Database $db) { $this->db = $db; } public function fetch() { return $this->db->query($this->sql())->rows(); } public function sql() { return "SELECT..."; } } 

Bu, bu raporla ilgili mantığımı tek bir sınıfta tutar ve kontrol etmesi kolaydır. Ruhun derinliklerine atlayabilir veya başka bir uygulamayı tam olarak kullanabilirim.

kontrolör

Şimdi eğlenceli olan kısım tüm parçaları bir araya getirmektir. Lütfen bağımlılık enjeksiyonunu kullandığımı unutmayın. Genellikle bağımlılıklar yapıcıya dahil edilir, ancak aslında onları doğrudan denetleyici yöntemlerime (yollar) girmeyi tercih ederim. Bu, denetleyicinin nesnesinin zamanlamasını en aza indirir ve ben onu daha net buluyorum. Lütfen bu yaklaşımı beğenmediyseniz, sadece geleneksel yapıcı yöntemini kullanın.

 class UsersController { public function index(AllUsersQueryInterface $query) { // Fetch user data $users = $query->fetch(['first_name', 'last_name', 'email']); // Return view return Response::view('all_users.php', ['users' => $users]); } public function add() { return Response::view('add_user.php'); } public function insert(UserRepositoryInterface $repository) { // Create new user model $user = new User; $user->first_name = $_POST['first_name']; $user->last_name = $_POST['last_name']; $user->gender = $_POST['gender']; $user->email = $_POST['email']; // Save the new user $repository->save($user); // Return the id return Response::json(['id' => $user->id]); } public function view(SpecificUserQueryInterface $query, $id) { // Load user data if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) { return Response::notFound(); } // Return view return Response::view('view_user.php', ['user' => $user]); } public function edit(SpecificUserQueryInterface $query, $id) { // Load user data if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) { return Response::notFound(); } // Return view return Response::view('edit_user.php', ['user' => $user]); } public function update(UserRepositoryInterface $repository) { // Load user model if (!$user = $repository->find($id)) { return Response::notFound(); } // Update the user $user->first_name = $_POST['first_name']; $user->last_name = $_POST['last_name']; $user->gender = $_POST['gender']; $user->email = $_POST['email']; // Save the user $repository->save($user); // Return success return true; } public function delete(UserRepositoryInterface $repository) { // Load user model if (!$user = $repository->find($id)) { return Response::notFound(); } // Delete the user $repository->delete($user); // Return success return true; } } 

Son düşünceler:

Nesneleri değiştirdiğimde (yarattığımda, güncellediğimde veya sildiğimde), modelin gerçek nesneleriyle çalıştığımı ve depolarımda ısrarcı olduğumu not etmek önemlidir.

Bununla birlikte, gösterdiğimde (veri seçip görünümlere gönderirken), model nesnelerle çalışmam, daha ziyade eski değer nesnelerle çalışıyorum. Yalnızca ihtiyacım olan alanları seçiyorum ve veri alma performansını en üst düzeye çıkarabilmek için oluşturulmuştur.

Depolarım çok temiz kalıyor ve bunun yerine bu “karışıklık” benim model isteklerime göre düzenleniyor.

Gelişmeye yardımcı olmak için bir veri haritası kullanıyorum, çünkü ortak görevler için yinelenen SQL yazmak çok saçma. Ancak, gerektiğinde kesinlikle SQL yazabilirsiniz (karmaşık sorgular, raporlar, vb.). Ve bunu yaparken, sınıfı uygun adla güzelce temizledi.

Yaklaşımımı nasıl aldığınızı gerçekten duymak isterim!


Temmuz 2015 Güncellemesi:

Her şeyi bittiğim yorumlarda bana soru soruldu. O kadar uzakta değil. Açıkçası, ben hala depoları sevmiyorum. Onları temel arama için aşırı buluyorum (özellikle zaten ORM kullanıyorsanız) ve rastgele daha karmaşık sorgularla çalışıyorum.

Genelde ORM ActiveRecord ile çalışıyorum, bu yüzden çoğu zaman bu modellere doğrudan tüm uygulamaya bakıyorum. Ancak, daha karmaşık sorgularım olduğu durumlarda, onları daha tekrar kullanılabilir hale getirmek için sorgu nesnelerini kullanacağım. Modellerimi her zaman yöntemlerime eklediğime de dikkat etmeliyim ki bu da testlerimde alay etmeyi kolaylaştırıyor.

174
26 апр. Cevapla Jonathan Apr 26 2013-04-26 16:45 '13, 16:45 2013-04-26 16:45

Tecrübelerime dayanarak, sorularınızın cevapları:

S: İhtiyacımız olmayan alanların iadesi ile nasıl başa çıkacağız?

C: Tecrübelerime göre, özel taleplere kıyasla tam tüzel kişilerle çalışmaktan geliyor.

Tam varlık bir User nesnesi gibi bir şeydir. Özellikleri ve yöntemleri var. O kod üssünde birinci sınıf bir vatandaş.

Geçici istek bazı verileri döndürüyor, ancak hiçbir şey bilmiyoruz. Veriler uygulama etrafında iletildiğinden, bu bağlam olmadan yapılır. Bu User mı? Bazı Order bilgisi olan bir User mı? Gerçekten bilmiyoruz.

Komple nesnelerle çalışmayı tercih ederim.

Sık kullanmayacağınız verileri döndüreceğiniz konusunda haklısınız, ancak bu verileri çeşitli şekillerde çözebilirsiniz:

  • Agresif bir şekilde nesneler önbelleğe alın, böylece veritabanından yalnızca bir kez okumak için ücret ödersiniz.
  • Nesnelerinizi modelleyerek daha fazla zaman harcayın, böylece aralarında iyi farklılıklar olur. (Büyük bir varlığı iki küçük nesneye vs. ayırmayı düşünün.).
  • Varlıkların birkaç versiyonunu göz önünde bulundurun. Arka uç için User ve AJAX aramaları için belki UserSmall sahip olabilirsiniz. Bunlardan biri 10, diğeri 3 özellikte olabilir.

Geçici isteklerle çalışmanın dezavantajları:

  • Sonuç olarak, birçok istek için aynı verileri elde edersiniz. Örneğin, User birçok çağrı için aslında aynı select * yazıyorsunuz. Bir arama 10 alandan 8'ini, bir tanesi 10'un 5'ini, bir tanesi 10'un 7'sini alacak. Neden hepsini 10'un 10'unu alan bir çağrı ile değiştirmiyorsunuz? Bunun kötü olmasının nedeni yeniden faktör / test / düzen için öldürmektir.
  • Zamanla kodunuz hakkında konuşmak çok zor. " User neden bu kadar yavaş?" Gibi ifadeler yerine. bir defalık talepleri takip edersiniz ve bu nedenle hata düzeltmeleri küçük ve yerelleştirilir.
  • Temel teknolojiyi değiştirmek çok zor. Artık her şeyi MySQL'de saklıyorsanız ve MongoDB'ye geçmek istiyorsanız, 100 özel aramayı değiştirmek birkaç varlıktan çok daha zordur.

S: Depomda çok fazla yöntem olacak.

C: Bunun gibi bir şey görmedim ama çağrıları birleştiriyorum. Deponuzda çağırılan yöntem gerçekten uygulamanızın işlevlerine karşılık gelir. İşlevler arttıkça, belirli verilerle ilişkili çağrılar da artar. İşlevlere dönebilir ve benzer aramaları bir araya getirmeyi deneyebilirsiniz.

Günün sonunda zorluk bir yerde bulunmalıdır. Depo şablonunu kullanarak, bir sürü saklı yordam oluşturmak yerine depo arabirimine taşıdık.

Bazen kendime şunu söylemeliyim: "Peki, bu size bir yere vermiş olmalı! Gümüş mermi yok."

42
24 апр. Cevap Ryan1234 24 nisan verilir . 2013-04-24 01:33 '13, 1:33 2013-04-24 01:33

Aşağıdaki arayüzleri kullanıyorum:

  • Repository - nesneleri yükler, ekler, günceller ve siler
  • Selector - depodaki filtrelere dayalı nesneleri bulur
  • Filter - filtreleme mantığını kapsüller

Repository - veritabanı agnostiği; aslında, istikrarı göstermez; herhangi bir şey olabilir: SQL veritabanı, xml dosyası, uzaktan servis, uzaydan gelen yabancı, vb. Arama yetenekleri için Repository , filtrelenebilen, LIMIT sıralanan, sıralanmış ve sayılan bir Selector oluşturur. Sonunda, seçici sabit bir veya daha fazla Entities seçer.

İşte bir örnek kod:

13
18 авг. Cevap Constantin Galbenu 18 Ağustos tarafından verildi . 2016-08-18 12:45 '16, 12:45, 2016-08-18 12:45

Şu an kendimi anlamaya çalıştığım için biraz ekleyeceğim.

# 1 ve 2

ORM'nizin ağır bir asansör yapması için mükemmel bir yer. Bir tür ORM uygulayan bir model kullanıyorsanız, bu şeylerle ilgilenmek için yöntemlerini kullanabilirsiniz. Gerekirse Eloquent yöntemlerini uygulayan özel OrderBy işlevleri oluşturun. Eloquent kullanarak, örneğin:

 class DbUserRepository implements UserRepositoryInterface { public function findAll() { return User::all(); } public function get(Array $columns) { return User::select($columns); } 

Aradığın şey bir ORM. Нет причин, по которым ваш репозиторий не может быть основан на одном. Это потребует от пользователя красноречия, но я лично не вижу в этом проблемы.