Servis Tanımları
Yapılandırma, DI container'a tek tek servisleri nasıl oluşturacağını ve onları bağımlılıklarıyla nasıl bağlayacağını anlattığımız yerdir. Nette bunu yapmanın çok anlaşılır ve şık bir yolunu sunar.
NEON yapılandırma dosyasındaki services bölümü, kendi servislerimizi ve yapılandırmalarını
tanımladığımız yerdir. PDO sınıfının bir örneğini temsil eden database adlı bir servisi
tanımlayan basit bir örneğe bakalım:
services:
database: PDO('sqlite::memory:')
Yukarıdaki yapılandırma, DI container içinde şu factory metodunu doğurur:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
Servis adları, yapılandırma dosyasının başka yerlerinde @servisAdı biçimiyle onlara başvurmayı sağlar.
Servise ad vermeye gerek yoksa yalnızca bir madde imi (-) kullanabiliriz:
services:
- PDO('sqlite::memory:')
DI container'dan bir servis almak için, parametre olarak servis adını alan getService() metodunu ya da servis
türünü alan getByType() metodunu kullanabiliriz:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Servis Oluşturma
Genellikle bir servisi yalnızca belirli bir sınıfı örnekleyerek oluştururuz. Örneğin:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Yapılandırmayı ek anahtarlarla genişletmemiz gerekirse, tanım birden çok satıra bölünebilir:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
create anahtarının factory adında bir takma adı vardır; iki biçim de yaygın olarak
kullanılır. Yine de create kullanmanızı öneririz.
Yapıcının ya da factory metodunun argümanları, alternatif olarak arguments anahtarıyla da
belirtilebilir:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Servisler yalnızca sınıf örneklemesiyle oluşturulmak zorunda değildir; statik metotların ya da başka servislerin metotlarının çağrılmasının sonucu da olabilirler:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Basitlik için -> yerine :: kullanıldığına dikkat edin, bkz. İfade
Dili. Şu factory metotları üretilecek:
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
DI container'ın, oluşturulan servisin türünü bilmesi gerekir. Servisi, dönüş türü belirtilmemiş bir metotla oluşturuyorsak, bu türü yapılandırmada açıkça bildirmeliyiz:
services:
database:
create: DatabaseFactory::create()
type: PDO
Argümanlar
Argümanları yapıcılara ve metotlara, PHP'nin kendisinde yapıldığına çok benzer biçimde aktarırız:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Daha iyi okunurluk için argümanları ayrı satırlarda sıralayabiliriz. Bu durumda virgül kullanmak isteğe bağlı olur:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Argümanları adlandırabilir ve böylece sıralarıyla uğraşmaktan kurtulabilirsiniz:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Belirli argümanları atlayıp varsayılan değerlerini kullanmak ya da autowiring ile bir servisin enjekte edilmesini istiyorsanız,
alt çizgi (_) kullanın:
services:
foo: Foo(_, %appDir%)
Argümanlar servisleri, parametreleri ve daha fazlasını içerebilir, bkz. İfade Dili.
Setup
setup bölümünde, servis oluşturulurken çağrılması gereken metotları tanımlarız.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
Bu PHP'de şöyle görünürdü:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Metot çağrılarının yanı sıra özelliklere değer de atanabilir. Dizilere öğe eklemek de desteklenir; bu, NEON söz dizimiyle çakışmayı önlemek için dizi erişiminin tırnak içine alınmasını gerektirir:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Bu, PHP kodunda şöyle görünürdü:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
Ancak setup içinde statik metotları ya da başka servislerin metotlarını da çağırabilirsiniz. Geçerli servisin
kendisini argüman olarak aktarmanız gerekiyorsa, ona @self ile başvurun:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Basitlik için -> yerine :: kullanıldığına dikkat edin, bkz. İfade
Dili. Şöyle bir factory metodu üretilecek:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
İfade Dili
Nette DI son derece zengin bir ifade dili sunar; bu dille neredeyse her şeyi tanımlayabiliriz. Yapılandırma dosyalarında parametreleri kullanabiliriz:
# parametre
%wwwDir%
# bir anahtarın altındaki parametrenin değeri
%mailer.user%
# dize içinde parametre
'%wwwDir%/images'
Ayrıca nesne oluşturabilir, metot ve fonksiyon çağırabiliriz:
# nesne oluştur
DateTime()
# statik metot çağır
Collator::create(%locale%)
# PHP fonksiyonu çağır
::getenv(DB_USER)
Servislere ya adlarıyla ya da türleriyle başvurabiliriz:
# ada göre servis
@database
# türe göre servis
@Nette\Database\Connection
First-class callable söz dizimini kullanın:
# callback oluşturur, [@user, logout] ile eşdeğerdir
@user::logout(...)
Sabitleri kullanın:
# sınıf sabiti
FilesystemIterator::SKIP_DOTS
# genel sabiti PHP'nin constant() fonksiyonuyla al
::constant(\PHP_VERSION)
Bir servisin public özelliklerine ve sabitlerine @service::member ile erişin. Adın bir özelliğe mi yoksa bir
sabite mi çözüleceğine ilk harfi karar verir: küçük harfle başlaması public bir özellik, büyük harfle başlaması bir
sabit demektir:
# bir servisin public özelliği (küçük harfle başlar)
@settings::apiUrl
# bir servisin sınıf sabiti (büyük harfle başlar)
@settings::Version
Metot çağrıları, tıpkı PHP'deki gibi zincirlenebilir. Basitlik için -> yerine ::
kullanılır:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Bu ifadeleri her yerde kullanabilirsiniz: servis oluştururken, argümanlarda, setup bölümünde ya da parametrelerde:
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Özel Fonksiyonlar
Yapılandırma dosyalarında şu özel fonksiyonları kullanabilirsiniz:
not()bir değeri tersine çevirirbool(),int(),float(),string()belirtilen türe kayıpsız dönüştürmetyped()belirtilen türdeki tüm servislerden oluşan bir dizi oluştururtagged()verilen etikete sahip tüm servislerden oluşan bir dizi oluşturur
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
(int) gibi standart PHP dönüşümlerinin aksine, kayıpsız dönüştürme sayısal olmayan değerlerde istisna
fırlatır.
typed() fonksiyonu, belirtilen türdeki (sınıf ya da arayüz) tüm servislerden oluşan bir dizi oluşturur.
Autowiring'i kapatılmış servisleri dışarıda bırakır. Virgülle ayırarak birden çok tür de belirtilebilir.
services:
- BarsDependent( typed(Bar) )
Belirli bir türdeki servislerden oluşan bir dizi, autowiring ile argüman olarak otomatik de aktarılabilir.
tagged() fonksiyonu ise belirli bir etikete sahip tüm servislerden oluşan bir dizi oluşturur. Burada da
virgülle ayırarak birden çok etiket belirtebilirsiniz.
services:
- LoggersDependent( tagged(logger) )
Autowiring
autowired anahtarı, belirli bir servisin autowiring davranışını etkilemenizi sağlar. Ayrıntılar için autowiring bölümüne bakın.
services:
foo:
create: Foo
autowired: false # foo servisi autowiring'den çıkarılır
Tembel Servisler
Tembel yükleme, bir servisin oluşturulmasını gerçekten gerekene dek erteleyen bir tekniktir. Genel yapılandırmada tüm servisler için tembel oluşturmayı tek seferde açabilirsiniz. Tek tek servislerde bu davranışı geçersiz kılabilirsiniz:
services:
foo:
create: Foo
lazy: false
Bir servis tembel olarak tanımlandığında, onu DI container'dan istediğimizde özel bir proxy nesnesi alırız. Bu proxy, gerçek servisle aynı görünür ve aynı davranır, ama asıl başlatma (yapıcının ve setup çağrılarının çalıştırılması) yalnızca metotlarından ya da özelliklerinden birine ilk erişimde gerçekleşir.
Servis daha sonra oluşturulduğundan, yapılandırmasındaki hataların da daha sonra ortaya çıkacağını unutmayın. Örneğin yanlış veritabanı kimlik bilgileri, uygulama başlarken değil, ilk sorguda kendini gösterir.
Tembel oluşturma, döngüsel bağımlılıkları da, yani A servisinin B'yi, B'nin de aynı anda A'yı gerektirdiği durumu
hafifletir. Bu olmadan container Circular reference detected hatasını bildirir. Tembel proxy'yle A servisi
yalnızca B servisinin bir proxy'sini alır; o da gerçekten kullanıldığında, yani A zaten varken kendini başlatır. Yine
de döngüsel bağımlılık kusurlu bir tasarımın işaretidir ve ondan kurtulmak daha iyidir.
Tembel yükleme PHP 8.4 ya da daha yenisini gerektirir ve yalnızca doğrudan sınıf örneklenerek oluşturulan
servislerde çalışır (örneğin create: Foo), factory metoduyla oluşturulanlarda çalışmaz. Sonunda PHP'nin iç
sınıflarından birini genişleten sınıflarda da kullanılamaz. Tembel yükleme uygulanamadığında lazy: true
bayrağı sessizce yok sayılır.
Etiketler
Etiketler, servislere ek bilgi eklemeye yarar. Bir servise bir ya da daha çok etiket atayabilirsiniz:
services:
foo:
create: Foo
tags:
- cached
Etiketler değer de taşıyabilir:
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
Belirli etiketlerle ilişkili tüm servisleri almak için tagged() fonksiyonunu kullanabilirsiniz:
services:
- LoggersDependent( tagged(logger) )
DI container içinde, belirli bir etikete sahip tüm servislerin adlarını findByTag() metoduyla
alabilirsiniz:
$names = $container->findByTag('logger');
// $names, anahtarları servis adları, değerleri etiket değerleri olan bir dizidir
// örneğin ['foo' => 'monolog.logger.event', ...]
Inject Kipi
inject: true bayrağı, Inject attribute'una sahip
public özellikler ve inject*()
metotları üzerinden bağımlılık enjeksiyonunu etkinleştirir.
services:
articles:
create: App\Model\Articles
inject: true
Varsayılan olarak inject kipi yalnızca presenter'larda açıktır.
Servis Değişiklikleri
DI container, yerleşik ya da kullanıcı
extension'larıyla eklenmiş pek çok servis tutar. Bu var olan servislerin tanımlarını doğrudan yapılandırmada
değiştirebilirsiniz. Örneğin varsayılanı Nette\Application\Application olan
application.application servisinin sınıfını başka bir sınıfla değiştirebilirsiniz:
services:
application.application:
create: MyApplication
alteration: true
alteration bayrağı, var olan bir servisi yalnızca değiştirdiğimizi gösterir. Aynı zamanda bir güvence
görevi görür: değiştirilen servis yoksa derleme istisnayla başarısız olur.
Setup'ı da tamamlayabiliriz:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
Bir servisi iç adıyla belirtmek zorunda değilsiniz; onun yerine türüyle başvurabilirsiniz. Önceki örnek şöyle de yazılabilir:
services:
@Nette\Application\Application:
create: MyApplication
Bir servisi değiştirirken özgün argümanları, setup öğelerini ya da etiketleri reset anahtarıyla
kaldırmak isteyebiliriz:
services:
application.application:
create: MyApplication
alteration: true
reset:
arguments: true
setup: true
tags: true
Bir extension tarafından eklenen bir servisi kaldırmak isterseniz bunu şöyle yapabilirsiniz:
services:
cache.journal: false