Uygulamanın Dizin Yapısı

Nette Framework'te projeler için net ve ölçeklenebilir bir dizin yapısı nasıl tasarlanır? Kodunuzu düzenlemenize yardımcı olacak kanıtlanmış uygulamaları göstereceğiz. Öğreneceksiniz:

  • uygulamanın dizinler halinde mantıksal olarak nasıl yapılandırılacağı
  • Proje büyüdükçe yapının iyi ölçeklendirilecek şekilde nasıl tasarlanacağı
  • olası alternatifler** ve bunların avantajları ya da dezavantajları nelerdir?

Nette Framework'ün kendisinin herhangi bir özel yapıda ısrar etmediğini belirtmek önemlidir. Her türlü ihtiyaç ve tercihe kolayca uyarlanabilecek şekilde tasarlanmıştır.

Temel Proje Yapısı

Nette Framework herhangi bir sabit dizin yapısı dikte etmese de, Web Projesi şeklinde kanıtlanmış bir varsayılan düzenleme vardır:

web-project/
├── app/              ← uygulama dizini
├── assets/           ← SCSS, JS dosyaları, resimler..., alternatif olarak kaynaklar/
├── bin/              ← komut satırı komut dosyaları
├── config/           ← yapılandırma
├── log/              ← günlüğe kaydedilen hatalar
├── temp/             ← geçici dosyalar, önbellek
├── tests/            ← testler
├── vendor/           ← Composer tarafından yüklenen kütüphaneler
└── www/              ← genel dizin (document-root)

Bu yapıyı ihtiyaçlarınıza göre serbestçe değiştirebilirsiniz – klasörleri yeniden adlandırabilir veya taşıyabilirsiniz. O zaman sadece Bootstrap.php ve muhtemelen composer.json adresindeki dizinlerin göreli yollarını ayarlamanız gerekir. Başka hiçbir şeye gerek yok, karmaşık yeniden yapılandırma yok, sürekli değişiklik yok. Nette akıllı otomatik algılamaya sahiptir ve URL tabanı da dahil olmak üzere uygulama konumunu otomatik olarak tanır.

Kod Organizasyon İlkeleri

Yeni bir projeyi ilk keşfettiğinizde, kendinizi hızlı bir şekilde yönlendirebilmelisiniz. app/Model/ dizinine tıkladığınızı ve bu yapıyı gördüğünüzü hayal edin:

app/Model/
├── Services/
├── Repositories/
└── Entities/

Buradan yalnızca projenin bazı hizmetleri, depoları ve varlıkları kullandığını öğreneceksiniz. Başvurunun asıl amacı hakkında hiçbir şey öğrenemezsiniz.

Şimdi farklı bir yaklaşıma bakalım – alanlara göre düzenleme:

app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/

Bu farklı – ilk bakışta bunun bir e-ticaret sitesi olduğu açık. Dizin adları, uygulamanın ne yapabileceğini ortaya koyuyor – ödemeler, siparişler ve ürünlerle çalışıyor.

İlk yaklaşım (sınıf türüne göre düzenleme) pratikte çeşitli sorunlara yol açar: mantıksal olarak ilişkili olan kod farklı klasörlere dağılmıştır ve bunlar arasında geçiş yapmanız gerekir. Bu nedenle, alanlara göre düzenleyeceğiz.

İsim Alanları

Dizin yapısının uygulamadaki isim alanlarına karşılık gelmesi gelenekseldir. Bu, dosyaların fiziksel konumlarının isim alanlarına karşılık geldiği anlamına gelir. Örneğin, app/Model/Product/ProductRepository.php adresinde bulunan bir sınıf App\Model\Product ad alanına sahip olmalıdır. Bu ilke, kod oryantasyonuna yardımcı olur ve otomatik yüklemeyi basitleştirir.

İsimlerde Tekil ve Çoğul

Ana uygulama dizinleri için tekil kullandığımıza dikkat edin: app, config, log, temp, www. Aynı şey uygulama içinde de geçerlidir: Model, Core, Presentation. Bunun nedeni, her birinin birleşik bir kavramı temsil etmesidir.

Benzer şekilde, app/Model/Product ürünler hakkında her şeyi temsil eder. Buna Products demiyoruz çünkü ürünlerle dolu bir klasör değil ( iphone.php, samsung.php gibi dosyalar içerirdi). Ürünlerle çalışmak için sınıflar içeren bir ad alanıdır – ProductRepository.php, ProductService.php.

app/Tasks klasörü çoğuldur çünkü bir dizi bağımsız çalıştırılabilir komut dosyası içerir – CleanupTask.php, ImportTask.php. Her biri bağımsız bir birimdir.

Tutarlılık için kullanmanızı öneririz:

  • Bir işlevsel birimi temsil eden ad alanları için tekil (birden fazla varlık ile çalışılsa bile)
  • Bağımsız birim koleksiyonları için çoğul
  • Belirsizlik durumunda veya bu konuda düşünmek istemiyorsanız, tekil seçin

Genel Rehber www/

Bu dizin web'den erişilebilen tek dizindir (belge kökü olarak adlandırılır). Sık sık www/ yerine public/ adıyla karşılaşabilirsiniz – bu sadece bir kural meselesidir ve işlevselliği etkilemez. Dizin şunları içerir:

  • Uygulama giriş noktası index.php
  • .htaccess mod_rewrite kurallarını içeren dosya (Apache için)
  • Statik dosyalar (CSS, JavaScript, resimler)
  • Yüklenen dosyalar

Doğru uygulama güvenliği için document-root'un doğru yapılandırılmış olması çok önemlidir.

node_modules/ klasörünü asla bu dizine yerleştirmeyin – çalıştırılabilir olabilecek ve herkesin erişimine açık olmaması gereken binlerce dosya içerir.

Uygulama Dizini app/

Bu, uygulama kodunun bulunduğu ana dizindir. Temel yapı:

app/
├── Core/               ← altyapi konulari
├── Model/              ← iş mantığı
├── Presentation/       ← sunum yapan ki̇şi̇ler ve şablonlar
├── Tasks/              ← komut dosyaları
└── Bootstrap.php       ← uygulama önyükleme sınıfı

Bootstrap.php ortamı başlatan, yapılandırmayı yükleyen ve DI konteynerini oluşturan uygulama başlangıç sınıfıdır.

Şimdi tek tek alt dizinlere ayrıntılı olarak bakalım.

Sunucular ve Şablonlar

Uygulamanın sunum kısmını app/Presentation dizininde bulunduruyoruz. Bir alternatif de kısa app/UI. Burası tüm sunum yapanların, şablonlarının ve yardımcı sınıfların yeridir.

Bu katmanı etki alanlarına göre düzenliyoruz. E-ticaret, blog ve API'yi birleştiren karmaşık bir projede, yapı şu şekilde görünecektir:

app/Presentation/
├── Shop/              ← e-ticaret ön yüzü
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← YÖNETİM
│   ├── Dashboard/
│   └── Products/
└── Api/               ← API uç noktaları
	└── V1/

Tersine, basit bir blog için bu yapıyı kullanırız:

app/Presentation/
├── Front/             ← web sitesi ön yüzü
│   ├── Home/
│   └── Post/
├── Admin/             ← YÖNETİM
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, site haritaları vb.

Home/ veya Dashboard/ gibi klasörler sunum yapan kişileri ve şablonları içerir. Front/ , Admin/ veya Api/ gibi klasörler modüller olarak adlandırılır. Teknik olarak, bunlar uygulamanın mantıksal organizasyonuna hizmet eden normal dizinlerdir.

Sunum yapan kişinin bulunduğu her klasör, benzer şekilde adlandırılmış bir sunum yapan kişi ve onun şablonlarını içerir. Örneğin, Dashboard/ klasörü şunları içerir:

Dashboard/
├── DashboardPresenter.php     ← SUNUCU
└── default.latte              ← şablon

Bu dizin yapısı sınıf ad alanlarına yansıtılır. Örneğin, DashboardPresenter, App\Presentation\Admin\Dashboard ad alanındadır (bkz. sunucu eşleme):

namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	//...
}

Uygulamadaki Admin modülünün içindeki Dashboard sunumcusuna iki nokta üst üste gösterimini kullanarak Admin:Dashboard şeklinde atıfta bulunuyoruz. default eylemine daha sonra Admin:Dashboard:default olarak. İç içe modüller için daha fazla iki nokta üst üste kullanırız, örneğin Shop:Order:Detail:default.

Esnek Yapı Geliştirme

Bu yapının en büyük avantajlarından biri, büyüyen proje ihtiyaçlarına ne kadar zarif bir şekilde uyum sağlamasıdır. Örnek olarak, XML beslemeleri üreten bölümü ele alalım. Başlangıçta basit bir formumuz var:

Export/
├── ExportPresenter.php   ← tüm ihracatlar için bir sunumcu
├── sitemap.latte         ← site haritası için şablon
└── feed.latte            ← RSS beslemesi için şablon

Zamanla daha fazla besleme türü eklenir ve bunlar için daha fazla mantığa ihtiyacımız olur… Sorun değil! Export/ klasörü basitçe bir modül haline gelir:

Export/
├── Sitemap/
│   ├── SitemapPresenter.php
│   └── sitemap.latte
└── Feed/
	├── FeedPresenter.php
	├── amazon.latte         ← Amazon için besleme
	└── ebay.latte           ← eBay için besleme

Bu dönüşüm tamamen sorunsuzdur – sadece yeni alt klasörler oluşturun, kodu bunlara bölün ve bağlantıları güncelleyin (örneğin Export:feed adresinden Export:Feed:amazon adresine). Bu sayede yapıyı gerektiği gibi kademeli olarak genişletebiliriz, yuvalama seviyesi hiçbir şekilde sınırlı değildir.

Örneğin, yönetimde OrderDetail, OrderEdit, OrderDispatch vb. gibi sipariş yönetimi ile ilgili birçok sunumcunuz varsa, daha iyi organizasyon için Detail, Edit, Dispatch ve diğer sunumcuları (klasörleri) içeren bir modül (klasör) Order oluşturabilirsiniz.

Şablon Konumu

Önceki örneklerde, şablonların doğrudan sunum yapan kişinin bulunduğu klasörde yer aldığını görmüştük:

Dashboard/
├── DashboardPresenter.php     ← SUNUCU
├── DashboardTemplate.php      ← isteğe bağlı şablon sınıfı
└── default.latte              ← şablon

Bu konum pratikte en uygun konumdur – ilgili tüm dosyalar elinizin altındadır.

Alternatif olarak, şablonları bir templates/ alt klasörüne yerleştirebilirsiniz. Nette her iki seçeneği de destekler. Hatta şablonları Presentation/ klasörünün tamamen dışına da yerleştirebilirsiniz. Şablon konumu seçenekleriyle ilgili her şeyi Şablon Arama bölümünde bulabilirsiniz.

Yardımcı Sınıflar ve Bileşenler

Sunucular ve şablonlar genellikle başka yardımcı dosyalarla birlikte gelir. Bunları kapsamlarına göre mantıksal olarak yerleştiriyoruz:

1. Verilen sunum yapan kişi için özel bileşenler olması durumunda Doğrudan sunum yapan kişiyle:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← ürün listeleme için bileşen
└── FilterForm.php         ← filtreleme için form

2. Modül için – alfabenin başına düzgün bir şekilde yerleştirilmiş olan Accessory klasörünü kullanmanızı öneririz:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← ön uç için bileşenler
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. Başvurunun tamamı için – Presentation/Accessory/ adresinde :

app/Presentation/
├── Accessory/
│   ├── LatteExtension.php
│   └── TemplateFilters.php
├── Front/
└── Admin/

Ya da LatteExtension.php veya TemplateFilters.php gibi yardımcı sınıfları app/Core/Latte/ altyapı klasörüne yerleştirebilirsiniz. Ve bileşenleri app/Components. Seçim takım geleneklerine bağlıdır.

Model – Uygulamanın Kalbi

Model, uygulamanın tüm iş mantığını içerir. Organizasyonu için de aynı kural geçerlidir – etki alanlarına göre yapılandırırız:

app/Model/
├── Payment/                   ← ödemeler hakkında her şey
│   ├── PaymentFacade.php      ← ana giriş noktası
│   ├── PaymentRepository.php
│   ├── Payment.php            ← varlık
├── Order/                     ← si̇pari̇şler hakkinda her şey
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← nakli̇yat hakkinda her şey

Modelde genellikle bu tür sınıflarla karşılaşırsınız:

Cephe: uygulamadaki belirli bir alana ana giriş noktasını temsil eder. Tam kullanım durumlarını (“sipariş oluşturma” veya “ödeme işleme” gibi) uygulamak için farklı hizmetler arasındaki işbirliğini koordine eden bir orkestratör görevi görürler. Orkestrasyon katmanı altında, cephe uygulama ayrıntılarını uygulamanın geri kalanından gizler, böylece verilen alanla çalışmak için temiz bir arayüz sağlar.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// doğrulama
		// sipariş oluşturma
		// e-posta gönderme
		// i̇stati̇sti̇klere yazma
	}
}

Hizmetler: bir etki alanı içindeki belirli iş operasyonlarına odaklanır. Tüm kullanım durumlarını düzenleyen fasadların aksine, bir hizmet belirli iş mantığını uygular (fiyat hesaplamaları veya ödeme işleme gibi). Hizmetler genellikle durum bilgisi içermez ve daha karmaşık işlemler için yapı taşları olarak cepheler tarafından ya da daha basit görevler için doğrudan uygulamanın diğer bölümleri tarafından kullanılabilir.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// fi̇yat hesaplama
	}
}

Depolar: tipik olarak bir veritabanı olan veri depolama ile tüm iletişimi gerçekleştirir. Görevleri varlıkları yüklemek ve kaydetmek ve bunları aramak için yöntemler uygulamaktır. Depo, uygulamanın geri kalanını veritabanı uygulama ayrıntılarından korur ve verilerle çalışmak için nesne yönelimli bir arayüz sağlar.

class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}

Varlıklar: uygulamadaki ana iş kavramlarını temsil eden, kimlikleri olan ve zaman içinde değişen nesneler. Tipik olarak bunlar ORM (Nette Database Explorer veya Doctrine gibi) kullanılarak veritabanı tablolarına eşlenen sınıflardır. Varlıklar, verileri ve doğrulama mantıklarıyla ilgili iş kuralları içerebilir.

// Veritabanı tablosu siparişleriyle eşlenen varlık
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}

Değer nesneleri: kendi kimlikleri olmayan değerleri temsil eden değişmez nesneler – örneğin, bir para miktarı veya e-posta adresi. Bir değer nesnesinin aynı değerlere sahip iki örneği aynı kabul edilir.

Altyapı Kodu

Core/ klasörü (veya ayrıca Infrastructure/) uygulamanın teknik temeline ev sahipliği yapar. Altyapı kodu tipik olarak şunları içerir:

app/Core/
├── Router/               ← yönlendirme ve URL yönetimi
│   └── RouterFactory.php
├── Security/             ← kimlik doğrulama ve yetkilendirme
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← günlüğe kaydetme ve izleme
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← önbellekleme katmanı
│   └── FullPageCache.php
└── Integration/          ← harici hizmetlerle entegrasyon
	├── Slack/
	└── Stripe/

Daha küçük projeler için düz bir yapı doğal olarak yeterlidir:

Core/
├── RouterFactory.php
├── Authenticator.php
└── QueueMailer.php

Bu bir kod:

  • Teknik altyapıyı yönetir (yönlendirme, günlüğe kaydetme, önbelleğe alma)
  • Harici hizmetleri entegre eder (Sentry, Elasticsearch, Redis)
  • Tüm uygulama için temel hizmetleri sağlar (posta, veritabanı)
  • Çoğunlukla belirli bir etki alanından bağımsızdır – önbellek veya kaydedici, e-ticaret veya blog için aynı şekilde çalışır.

Belirli bir sınıfın buraya mı yoksa modele mi ait olduğunu merak ediyor musunuz? Temel fark, Core/ adresindeki koddur:

  • Alan hakkında hiçbir şey bilmiyor (ürünler, siparişler, makaleler)
  • Genellikle başka bir projeye aktarılabilir
  • “Nasıl çalıştığını” (nasıl posta gönderileceğini) çözer, “ne yaptığını” (hangi postanın gönderileceğini) değil

Daha iyi anlaşılması için örnek:

  • App\Core\MailerFactory – e-posta gönderme sınıfının örneklerini oluşturur, SMTP ayarlarını yapar
  • App\Model\OrderMailer – siparişler hakkında e-posta göndermek için MailerFactory adresini kullanır, şablonlarını ve ne zaman gönderilmeleri gerektiğini bilir

Komut Komut Dosyaları

Uygulamalar genellikle arka planda veri işleme, bakım veya periyodik görevler gibi normal HTTP istekleri dışında görevler gerçekleştirmeye ihtiyaç duyar. Gerçek uygulama mantığı app/Tasks/ (veya app/Commands/) dizinine yerleştirilirken, bin/ dizinindeki basit komut dosyaları yürütme için kullanılır.

Örnek:

app/Tasks/
├── Maintenance/               ← bakım komut dosyaları
│   ├── CleanupCommand.php     ← eski verileri silme
│   └── DbOptimizeCommand.php  ← veritabanı optimizasyonu
├── Integration/               ← harici sistemlerle entegrasyon
│   ├── ImportProducts.php     ← tedarikçi sisteminden içe aktar
│   └── SyncOrders.php         ← sipariş senkronizasyonu
└── Scheduled/                 ← düzenli görevler
	├── NewsletterCommand.php  ← haber bültenleri gönderme
	└── ReminderCommand.php    ← müşteri̇ bi̇ldi̇ri̇mleri̇

Ne modele ne de komut dosyalarına aittir? Örneğin, bir e-posta gönderme mantığı modelin bir parçasıdır, binlerce e-postanın toplu olarak gönderilmesi Tasks/ adresine aittir.

Görevler genellikle komut satırından veya cron aracılığıyla çalıştırılır. HTTP isteği yoluyla da çalıştırılabilirler, ancak güvenlik göz önünde bulundurulmalıdır. Görevi çalıştıran sunucunun, örneğin yalnızca oturum açmış kullanıcılar için veya güçlü bir belirteçle ve izin verilen IP adreslerinden erişimle güvenli hale getirilmesi gerekir. Uzun görevler için komut dosyası zaman sınırını artırmak ve oturumun kilitlenmesini önlemek için session_write_close() adresini kullanmak gerekir.

Diğer Olası Dizinler

Bahsedilen temel dizinlere ek olarak, proje ihtiyaçlarına göre başka özel klasörler de ekleyebilirsiniz. Şimdi en yaygın olanlara ve kullanımlarına bakalım:

app/
├── Api/              ← Sunum katmanından bağımsız API mantığı
├── Database/         ← test verileri için geçiş komut dosyaları ve tohumlayıcılar
├── Components/       ← uygulama genelinde paylaşılan görsel bileşenler
├── Event/            ← olay güdümlü mimari kullanılıyorsa kullanışlıdır
├── Mail/             ← e-posta şablonları ve ilgili mantık
└── Utils/            ← yardımcı sınıflar

Uygulama genelinde sunumlarda kullanılan paylaşılan görsel bileşenler için app/Components veya app/Controls klasörünü kullanabilirsiniz:

app/Components/
├── Form/                 ← paylaşılan form bileşenleri
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← veri listeleri için bileşenler
│   └── DataGrid.php
└── Navigation/           ← navigasyon öğeleri
	├── Breadcrumbs.php
	└── Menu.php

Burası daha karmaşık mantığa sahip bileşenlerin ait olduğu yerdir. Bileşenleri birden fazla proje arasında paylaşmak istiyorsanız, bunları bağımsız bir composer paketine ayırmanız iyi olur.

app/Mail dizinine e-posta iletişim yönetimini yerleştirebilirsiniz:

app/Mail/
├── templates/            ← e-posta şablonları
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Sunucu Haritalama

Eşleme, sınıf adlarının sunucu adlarından türetilmesi için kuralları tanımlar. Bunları yapılandırmada application › mapping anahtarı altında belirtiriz.

Bu sayfada, sunum yapan kişileri app/Presentation klasörüne (veya app/UI) yerleştirdiğimizi gösterdik. Nette'e yapılandırma dosyasında bu konvansiyon hakkında bilgi vermemiz gerekiyor. Bir satır yeterli:

application:
	mapping: App\Presentation\*\**Presenter

Eşleme nasıl çalışır? Daha iyi anlamak için öncelikle modülsüz bir uygulama hayal edelim. Sunucu sınıflarının App\Presentation ad alanı altında olmasını istiyoruz, böylece Home sunucusu App\Presentation\HomePresenter sınıfıyla eşleşir. Bu, bu yapılandırma ile elde edilir:

application:
	mapping: App\Presentation\*Presenter

Eşleme, App\Presentation\*Presenter maskesindeki yıldız işaretini Home sunum yapan kişi adıyla değiştirerek çalışır ve sonuçta App\Presentation\HomePresenter nihai sınıf adı elde edilir. Çok basit!

Ancak, bu ve diğer bölümlerdeki örneklerde gördüğünüz gibi, sunum sınıflarını isimsiz alt dizinlere yerleştiririz, örneğin Home sunum sınıfı App\Presentation\Home\HomePresenter sınıfına eşlenir. Bunu iki nokta üst üste işaretini iki katına çıkararak başarıyoruz (Nette Application 3.2 gerektirir):

application:
	mapping: App\Presentation\**Presenter

Şimdi sunum yapan kişileri modüllerle eşleştirmeye geçeceğiz. Her modül için özel eşleme tanımlayabiliriz:

application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter

Bu yapılandırmaya göre, Front:Home sunucusu App\Presentation\Front\Home\HomePresenter sınıfıyla eşleşirken, Api:OAuth sunucusu App\Api\OAuthPresenter sınıfıyla eşleşir.

Front ve Admin modülleri benzer bir eşleme yöntemine sahip olduğundan ve muhtemelen bu tür daha fazla modül olacağından, bunların yerini alacak genel bir kural oluşturmak mümkündür. Sınıf maskesine modül için yeni bir yıldız işareti eklenecektir:

application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter

Ayrıca, yıldız işaretli bölümün her seviye için tekrarlandığı ve App\Presentation\Admin\User\Edit\EditPresenter sınıfıyla sonuçlandığı Admin:User:Edit gibi daha derin iç içe dizin yapıları için de çalışır.

Alternatif bir gösterim, bir dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim bir öncekine eşdeğerdir:

application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]
versiyon: 4.0