Feragatname: Aşağıda, PHP tabanlı web uygulamaları bağlamında MVC benzeri kalıpları nasıl anladığımın bir açıklaması yer almaktadır. İçeriğinde kullanılan tüm harici linkler var terim ve kavramları açıklamak ve vardır değil konu üzerinde kendi itibarını ima etmek.
Temizlemem gereken ilk şey: model bir katman .
İkincisi: Klasik MVC ile web geliştirmede kullandıklarımız arasında bir fark vardır . İşte biraz daha farklı olduklarını kısaca anlatan daha eski bir cevap yazdım.
Bir model DEĞİLDİR:
Model bir sınıf veya tek bir nesne değildir. Yapmak çok yaygın bir hatadır (ben de yaptım, aksi halde öğrenmeye başladığımda orijinal cevap yazılmıştır) , çünkü çoğu çerçeve bu yanlış algıyı sürdürmektedir.
Ne Nesne İlişkisel Haritalama tekniği (ORM) ne de veritabanı tablolarının bir soyutlaması değildir. Size aksini söyleyen herkes büyük olasılıkla başka bir yepyeni ORM'yi veya tüm çerçeveyi 'satmaya' çalışıyor .
Bir model nedir:
Uygun MVC adaptasyon olarak, M tüm alan iş mantığını içerir ve Model Katman edilir çoğunlukla yapıların üç türlerinden yaptı:
Alan Nesneleri
Etki alanı nesnesi, yalnızca etki alanı bilgilerinin mantıksal bir kapsayıcısıdır; genellikle sorun etki alanındaki mantıksal bir varlığı temsil eder. Genellikle iş mantığı olarak adlandırılır .
Burası, bir fatura göndermeden önce verilerin nasıl doğrulanacağını veya bir siparişin toplam maliyetini nasıl hesaplayacağınızı tanımlar. Aynı zamanda, Alan Nesneler depolama tamamen habersiz - ne den nerede (SQL veritabanı, REST API, metin dosyası, vb) ne de olsa eğer onlar kaydedilen veya alınan olsun.
Veri Eşleyicileri
Bu nesneler yalnızca depolamadan sorumludur. Bir veritabanında bilgi depolarsanız, SQL burada yaşar. Veya veri depolamak için bir XML dosyası kullanırsınız ve Veri Eşleştiricileriniz XML dosyalarından ayrılır .
Hizmetler
Bunları "daha üst düzey Alan Nesneleri" olarak düşünebilirsiniz, ancak iş mantığı yerine Alan Adları Nesneleri ile Haritacılar arasındaki etkileşimden Hizmetler sorumludur . Bu yapılar, alan adı iş mantığı ile etkileşim için bir "genel" arayüz oluşturur. Onlardan kaçınabilirsiniz, ancak bazı etki alanı mantığını Denetleyicilere sızdırma cezasıyla .
EKL uygulama sorusunda bu konuya ilişkin bir cevap var - yararlı olabilir.
Model katman ile MVC triadının diğer bölümleri arasındaki iletişim sadece Servisler aracılığıyla yapılmalıdır . Açık bir ayrımın birkaç ek faydası vardır:
- tek sorumluluk ilkesinin (SRP) uygulanmasına yardımcı olur
- mantığın değişmesi durumunda ek 'kıpır kıpır oda' sağlar
- denetleyiciyi olabildiğince basit tutar
- harici bir API'ya ihtiyacınız varsa net bir plan sunar
Bir modelle nasıl etkileşim kurulur?
Ön şartlar: izle dersler "Küresel Devlet ve Singletons" ve "Things için Bakma!" Temiz Kod Görüşmelerinden.
Hizmet örneklerine erişim sağlama
Her ikisi için Görünüm ve Denetleyici erişim bu hizmetleri, iki genel yaklaşım vardır için: ( "UI katmanı" Sen buna ne olabilir) örnekleri:
- Gerekli hizmetleri, tercihen DI kabı kullanarak doğrudan görüş ve denetleyicilerinizin yapıcılarına enjekte edebilirsiniz.
- Tüm görünümleriniz ve denetleyicileriniz için zorunlu bir bağımlılık olarak hizmetler için bir fabrika kullanmak.
Şüphelendiğiniz gibi, DI kabı çok daha zarif bir çözümdür (yeni başlayanlar için en kolay olmasa da). Bu işlevselliği göz önünde bulundurmayı önerdiğim iki kütüphane Syfmony'in bağımsız DependencyInjection bileşeni veya Auryn olacaktır .
Hem fabrika hem de DI kapsayıcısı kullanan çözümler, seçilen denetleyici arasında paylaşılacak çeşitli sunucuların örneklerini paylaşmanıza ve belirli bir istek yanıt döngüsü için görünüm sağlamanıza da olanak tanır.
Modelin durumunun değiştirilmesi
Artık denetleyicilerdeki model katmanına erişebildiğinize göre, bunları gerçekten kullanmaya başlamanız gerekir:
public function postLogin(Request $request)
{
$email = $request->get('email');
$identity = $this->identification->findIdentityByEmailAddress($email);
$this->identification->loginWithPassword(
$identity,
$request->get('password')
);
}
Denetleyicilerinizin çok net bir görevi vardır: kullanıcı girdisini alın ve bu girdiye dayanarak geçerli iş mantığının durumunu değiştirin. Bu örnekte, arasında değiştirilen durumlar "anonim kullanıcı" ve "oturum açmış kullanıcı" dır.
Denetleyici, kullanıcının girişlerini doğrulamaktan sorumlu değildir, çünkü bu iş kurallarının bir parçasıdır ve denetleyici kesinlikle burada veya burada göreceğiniz gibi SQL sorgularını çağırmaz (lütfen onlardan nefret etmeyin, yanlış yönlendirilir, kötülük değildir).
Kullanıcıya durum değişikliği gösteriliyor.
Tamam, kullanıcı giriş yaptı (veya başarısız oldu). Şimdi ne olacak? Sözü edilen kullanıcı hala farkında değil. Yani aslında bir yanıt üretmelisiniz ve bu bir görüşün sorumluluğudur.
public function postLogin()
{
$path = '/login';
if ($this->identification->isUserLoggedIn()) {
$path = '/dashboard';
}
return new RedirectResponse($path);
}
Bu durumda, görünüm model katmanının mevcut durumuna göre iki olası yanıttan birini üretti. Farklı bir kullanım durumunda, "makalenin şu an seçili olanı" gibi bir şeye dayanarak, oluşturulacak farklı şablonlar seçme görünümünüz olur.
Sunu katmanı, burada açıklandığı gibi aslında oldukça ayrıntılı olabilir: PHP'deki MVC Görünümlerini Anlama .
Ama ben sadece bir REST API yapıyorum!
Tabii ki, bu aşırı bir ölüm olduğunda durumlar var.
MVC, Endişelerin Ayrılması ilkesi için somut bir çözümdür . MVC kullanıcı arayüzünü iş mantığından ayırır ve kullanıcı arayüzünde kullanıcı girişi ve sunum işlemlerini ayırır. Bu çok önemli. Çoğu zaman insanlar bunu bir "üçlü" olarak tanımlasa da, aslında üç bağımsız bölümden oluşmuyor. Yapı daha çok şöyle:
Bu, sunum katmanınızın mantığı varolmayana yakın olduğunda, pragmatik yaklaşımın onları tek katman olarak tutmak olduğu anlamına gelir. Ayrıca model katmanının bazı yönlerini önemli ölçüde basitleştirebilir.
Bu yaklaşımı kullanarak giriş örneği (bir API için) şu şekilde yazılabilir:
public function postLogin(Request $request)
{
$email = $request->get('email');
$data = [
'status' => 'ok',
];
try {
$identity = $this->identification->findIdentityByEmailAddress($email);
$token = $this->identification->loginWithPassword(
$identity,
$request->get('password')
);
} catch (FailedIdentification $exception) {
$data = [
'status' => 'error',
'message' => 'Login failed!',
]
}
return new JsonResponse($data);
}
Bu sürdürülebilir olmasa da, bir yanıt gövdesi oluşturmak için karmaşık bir mantığınız olduğunda, bu basitleştirme daha önemsiz senaryolar için çok yararlıdır. Ama uyarılmalıdır karmaşık sunum mantığı ile büyük codebases kullanmak çalışırken bu yaklaşım, bir kabus haline gelecektir.
Model nasıl oluşturulur?
Tek bir "Model" sınıfı olmadığından (yukarıda açıklandığı gibi), gerçekten "modeli oluşturmaz". Bunun yerine belirli yöntemleri uygulayabilen Hizmetler yapmaya başlıyorsunuz . Ve sonra Etki Alanı Nesneleri ve Eşleyicileri uygulayın .
Bir hizmet yöntemi örneği:
Yukarıdaki her iki yaklaşımda da tanımlama hizmeti için bu giriş yöntemi vardı. Aslında neye benzeyecekti. Ben yazdım bir kütüphaneden aynı işlevselliğin biraz değiştirilmiş bir sürümünü kullanıyorum .. çünkü tembel olduğum için:
public function loginWithPassword(Identity $identity, string $password): string
{
if ($identity->matchPassword($password) === false) {
$this->logWrongPasswordNotice($identity, [
'email' => $identity->getEmailAddress(),
'key' => $password, // this is the wrong password
]);
throw new PasswordMismatch;
}
$identity->setPassword($password);
$this->updateIdentityOnUse($identity);
$cookie = $this->createCookieIdentity($identity);
$this->logger->info('login successful', [
'input' => [
'email' => $identity->getEmailAddress(),
],
'user' => [
'account' => $identity->getAccountId(),
'identity' => $identity->getId(),
],
]);
return $cookie->getToken();
}
Gördüğünüz gibi, bu soyutlama düzeyinde verilerin nereden getirildiğine dair bir gösterge yoktur. Bu bir veritabanı olabilir, ama aynı zamanda test amacıyla sadece sahte bir nesne olabilir. Aslında bunun için kullanılan veri eşleştiriciler bile private
bu hizmetin yöntemlerinde gizlidir .
private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
$identity->setStatus($status);
$identity->setLastUsed(time());
$mapper = $this->mapperFactory->create(Mapper\Identity::class);
$mapper->store($identity);
}
Harita oluşturmanın yolları
Bir kalıcılık soyutlaması uygulamak için, en esnek yaklaşımlar üzerinde özel veri eşleştiriciler oluşturmaktır .
Gönderen: PoEAA kitabı
Uygulamada, belirli sınıflar veya üst sınıflarla etkileşim için uygulanırlar. Diyelim ki kodunuzda Customer
ve Admin
kodunuzda (her ikisi de bir User
üst sınıftan devralma ). Her ikisi de muhtemelen farklı alanlar içerdiğinden, ayrı bir eşleşen haritacıya sahip olacaktır. Ancak, paylaşılan ve yaygın olarak kullanılan işlemlerle de sonuçlanacaksınız. Örneğin: "son çevrimiçi görüldü" zamanının güncellenmesi . Ve mevcut haritacıları daha kıvrımlı hale getirmek yerine, daha pragmatik yaklaşım, yalnızca o zaman damgasını güncelleyen genel bir "Kullanıcı Haritacısı" na sahip olmaktır.
Bazı ek yorumlar:
Veritabanı tabloları ve modeli
Bazen bir veritabanı tablosu, Etki Alanı Nesnesi ve Eşleyici arasında doğrudan 1: 1: 1 bir ilişki olsa da, daha büyük projelerde beklediğinizden daha az yaygın olabilir:
Tek bir Etki Alanı Nesnesi tarafından kullanılan bilgiler farklı tablolardan eşlenebilirken, nesnenin veritabanında kalıcılığı yoktur.
Örnek: Aylık bir rapor oluşturuyorsanız. Bu, farklı tablolardan bilgi toplar, ancak MonthlyReport
veritabanında sihirli bir tablo yoktur .
Tek bir Eşleyici birden çok tabloyu etkileyebilir.
Örnek: Nesneden veri saklarken User
, bu Etki Alanı Nesnesi diğer etki alanı nesneleri - Group
örnekleri koleksiyonu içerebilir . Bunları değiştirmek ve saklamak durumunda User
, Veri Mapper güncellemesine ve / veya birden tablolarda girdileri eklemek gerekir.
Tek bir Etki Alanı Nesnesindeki veriler birden fazla tabloda depolanır.
Örnek: büyük sistemlerde (düşünün: orta ölçekli bir sosyal ağ), kullanıcı kimlik doğrulama verilerinin ve sık erişilen verilerin nadiren gerekli olan daha büyük içerik yığınlarından ayrı olarak depolanması pragmatik olabilir. Bu durumda hala tek bir User
sınıfınız olabilir, ancak içerdiği bilgiler tüm ayrıntıların getirilip getirilmemesine bağlı olacaktır.
Her Etki Alanı Nesnesi için birden fazla eşleyici olabilir
Örnek: Hem halka açık hem de yönetim yazılımı için paylaşılan kod tabanlı bir haber siteniz var. Ancak, her iki arabirim de aynı Article
sınıfı kullanırken, yönetimin içinde çok daha fazla bilgi bulunması gerekir. Bu durumda iki ayrı eşleyiciniz olur: "dahili" ve "harici". Her biri farklı sorgular gerçekleştirir, hatta farklı veritabanları kullanır (master veya slave'de olduğu gibi).
Görünüm bir şablon değil
MVC'deki görünüm örnekleri (desenin MVP varyasyonunu kullanmıyorsanız) sunum mantığından sorumludur. Bu, her Görünümün genellikle en az birkaç şablonla oynatılacağı anlamına gelir . Model Katmanından veri alır ve sonra alınan bilgilere dayanarak bir şablon seçer ve değerleri ayarlar.
Bundan faydalanabileceğiniz faydalardan biri yeniden kullanılabilirliktir. Bir ListView
sınıf oluşturursanız , iyi yazılmış bir kodla, aynı sınıfın bir makalenin altındaki kullanıcı listesi ve yorumların sunumunu vermesini sağlayabilirsiniz. Çünkü ikisi de aynı sunum mantığına sahip. Sadece şablon değiştirirsiniz.
Ya kullanabilirsiniz doğal PHP şablonlar veya bazı üçüncü taraf şablon motoru kullanmak. View örneklerini tamamen değiştirebilen bazı üçüncü taraf kitaplıkları da olabilir .
Cevabın eski versiyonu ne olacak?
Tek büyük değişiklik, eski sürümde Model olarak adlandırılan , aslında bir Hizmet olmasıdır . "Kütüphane benzetmesi" nin geri kalanı oldukça iyi bir şekilde devam ediyor.
Gördüğüm tek kusur, bunun gerçekten garip bir kütüphane olması, çünkü kitaptan bilgi döndürecek, ancak kitabın kendisine dokunmanıza izin vermeyecek, aksi takdirde soyutlama "sızmaya" başlayacaktı. Daha uygun bir benzetme düşünmem gerekebilir.
Görünüm ve Denetleyici örnekleri arasındaki ilişki nedir ?
MVC yapısı iki katmandan oluşur: ui ve model. UI katmanındaki ana yapılar görünüm ve denetleyicidir.
MVC tasarım deseni kullanan web siteleriyle uğraşırken, en iyi yol görünümler ve denetleyiciler arasında 1: 1 ilişki kurmaktır. Her görünüm, web sitenizdeki tüm bir sayfayı temsil eder ve söz konusu görünüm için gelen tüm istekleri işlemek üzere özel bir denetleyiciye sahiptir.
Örneğin, bir açılan makale temsil etmek, sahip olacağını \Application\Controller\Document
ve \Application\View\Document
. Bu, makalelerle uğraşmak söz konusu olduğunda UI katmanı için tüm ana işlevleri içerir (elbette makalelerle doğrudan ilişkili olmayan bazı XHR bileşenlerine sahip olabilirsiniz ) .