Alan sistemi (Field API)
Field API, içerik türlerine kod yazmadan özel alan eklemenin yoludur. Bir haber türüne "kaynak", bir etkinliğe "başlangıç tarihi", bir ürüne "fiyat" eklemek için şema değiştirmezsiniz; bir alan tanımı oluşturursunuz. Form, doğrulama, depolama, görüntüleme ve arama indeksi bu tanımdan türer.
Bu sistem Node (içerik) türleri içindir. Gerçek SQL sütunlu iş kayıtları (Resource) alanlarını Doctrine ile tanımlar; bkz. İçerik modeli.
Kavramlar
| Kavram | Açıklama |
|---|---|
| Bundle | Alanların bağlı olduğu içerik türü (post, page, portal_news) |
Alan tanımı (FieldDefinition) |
Bundle + ad + tür + etiket + ayarlar |
Alan türü (FieldTypeInterface) |
Davranış: normalleştirme, doğrulama, indeks, ayar şeması |
| Widget | O alanın formda hangi Symfony form türüyle düzenleneceği |
| Formatter / görüntüleme | Alanın sayfada nasıl çizileceği |
| Görüntüleme modu (view mode) | default, teaser, card: aynı içeriğin farklı bağlamlarda sunumu |
Alan tanımı özellikleri
| Özellik | Anlamı |
|---|---|
name |
Makine adı: küçük harf, rakam, alt çizgi; harfle başlar, en çok 63 karakter |
type |
Alan türü kimliği (aşağıda) |
label, help |
Etiket ve yardım metni |
required |
Zorunlu mu |
cardinality |
Değer sayısı: 1 tek, -1 sınırsız, ya da sabit sayı |
translatable |
Çeviriye göre farklı değer taşıyabilir mi |
queryable |
Düz alan indeksine yazılsın mı (filtre/sıralama için) |
fieldGroup |
Formda ve görüntülemede gruplama |
weight |
Sıra |
settings |
Türe özgü ayarlar (ör. max_length, choices) |
Alanlar AACP → Yapı → Alanlar ekranından yönetilir. Yetki: system.fields.manage.
Yerleşik alan türleri
| Tür | Ne saklar | Ayar / not |
|---|---|---|
text |
Tek satır metin | max_length |
textarea |
Çok satır düz metin | |
rich_text |
Zengin metin (metin biçimiyle) | default_format (varsayılan basic_html) |
integer |
Tam sayı | Düz indekste int olarak aranabilir |
decimal |
Ondalık sayı | Düz indekste decimal |
boolean |
Evet/hayır | |
date, datetime |
Tarih, tarih-saat | Düz indekste datetime |
email, url |
Doğrulanmış e-posta / URL | |
select |
Sabit listeden değer | Seçenekler değer|etiket satırları |
reference |
Başka bir varlığın kimliği (kullanıcı, belirli türde node, resource) | target izinli bir dizedir, ham sınıf adı asla verilmez |
image |
Medya kütüphanesinden bir image/* varlığın kimliği |
|
file |
Medya kütüphanesinden bir varlığın kimliği |
Türün gerçek kimliklerini görmek için: php cp-core/bin/console cp:debug.
Değer yaşam döngüsü
form girdisi ──▶ FieldValueNormalizer ──▶ FieldValidator ──▶ depolama (Node::data JSON)
(tür.normalize) (tür.validate) └─▶ düz indeks (queryable ise)
- Normalleştirme (
FieldValueNormalizer): tek karar noktasıdır. Yalnızca bir alan tanımının desteklediği anahtarlar üretilir; ham istek dizileri olduğu gibi birleştirilmez (mass-assignment koruması). - Doğrulama (
FieldValidator): normalleştirilmiş, temiz değer üzerinde çalışır. Çok değerli alanlarda sayıyı denetler. - Depolama: değerler
Node::dataJSON sütununa yazılır. - İndeks:
queryablealanlarnode_field_indextablosuna tipli değerle yazılır.
Formlar
Bir modülün kendi formuna bundle alanlarını eklemek tek satırdır:
$this->fieldForms->add($builder, 'portal_news', $locale);
FieldableFormBuilder::add bundle'ın alanlarını fields adlı bir alt form olarak ekler; bundle'ın hiç alanı yoksa hiçbir şey yapmaz. Tür → form türü eşlemesini FieldWidgetResolver yapar; çok değerli alanları FieldsFormType bir CollectionType ile sarar.
Görsel ve dosya alanları
image / file alanları bir varlık kimliği saklar. Formda field-media betiği önizleme, "Kütüphaneden seç" ve "Kaldır" düğmelerini çizer. Şablonunuzun importmap listesine sırayla ekleyin:
{{ importmap(['app', 'media-picker', 'field-media']) }}
media-picker önce, field-media sonra gelmelidir. Medya modülü yoksa alan düz bir kimlik kutusu olarak kalır.
Çok değerli alanlar
cardinality değeri -1 ya da 1'den büyük olan alanlar bir koleksiyon olur; aynı betik + Ekle ve × düğmelerini ekler.
Şablonda alanları tek tek çizmek
{% for child in form.fields|filter(c => c.vars.name not in ['image']) %}
{{ form_row(child) }}
{% endfor %}
Görüntüleme
Ön yüz şablonlarında Twig işlevleri kullanılır:
| İşlev | Ne yapar |
|---|---|
cp_field(node, 'summary') |
Tek alanı çizer (HTML) |
cp_field_value(node, 'summary') |
Ham değeri verir |
cp_fields(node, 'grup') |
Bir alan grubunun tamamını çizer |
cp_has_field(node, 'summary') |
Alan dolu mu |
cp_fields_preload(nodes) |
Bir listedeki varlık referanslarını toplu yükler |
cp_entity_view(node, 'teaser') |
Varlığı bir görüntüleme modunda çizer |
{% do cp_fields_preload(items) %}
{% for item in items %}
<article>
<h2>{{ item.title }}</h2>
{% if cp_has_field(item, 'summary') %}{{ cp_field(item, 'summary') }}{% endif %}
</article>
{% endfor %}
FieldRenderer her alanın view_capability ayarına uyar (bir alanı yalnızca belli yetenekteki kullanıcılara gösterebilirsiniz) ve ek içerik sorgusu tetiklemez: değerler varlığın alan torbasında durur, referanslar toplu yüklenir. Liste şablonlarında N+1'den kaçınmak için cp_fields_preload kullanın.
Görüntüleme modları cp-core/config/view_modes.yaml içinde tanımlıdır (default, teaser, card). Hangi alanın hangi modda hangi formatter ile çizileceği AACP → Yapı → Görüntüleme ekranından ayarlanır.
Modül kurulumunda alan oluşturma
Bir modül, kendi türleri için alanları kurulumda tanımlar. FieldDefinitionSeeder::ensure() yalnızca eksik alanı oluşturur; bir editörün AACP'den ince ayar yaptığı alanı asla ezmez. Alan silmek ayrı, bilinçli bir işlemdir.
$this->fields->ensure([
['bundle' => 'portal_news', 'name' => 'source', 'type' => 'text', 'label' => 'Kaynak'],
['bundle' => 'portal_news', 'name' => 'summary', 'type' => 'textarea', 'label' => 'Özet'],
]);
Kendi alan türünü yazmak
Bir hizmeti #[CpFieldType] ile işaretleyin; tür kimliği sınıfın statik id() metodundan gelir ve derleme sırasında FieldTypeRegistry'ye toplanır. Bir modülün türü yüklenemezse yalnızca o modülün türleri düşer; çekirdek seti etkilenmez.
#[CpFieldType]
final class PhoneFieldType extends AbstractFieldType
{
public static function id(): string { return 'phone'; }
public function label(): string { return 'field.type.phone'; }
public function normalize(mixed $raw, FieldContext $context): mixed
{
$digits = preg_replace('/\D+/', '', (string) $raw);
return $digits === '' ? null : $digits;
}
public function validate(mixed $value, FieldContext $context): array
{
return strlen((string) $value) < 10 ? ['field.phone.too_short'] : [];
}
}
AbstractFieldType güvenli varsayılanlar verir (indekslenemez, ayarsız, her zaman geçerli); yalnızca ihtiyaç duyduğunuz metotları geçersiz kılın:
| Metot | Görev |
|---|---|
normalize |
Ham girdiyi temiz depolama biçimine çevirir (tek değer üzerinde çalışır) |
validate |
Hata anahtarlarının listesini döndürür (boşsa geçerli) |
indexKind / indexValue |
Düz indekse nasıl yazılacağı (string, int, decimal, datetime) |
settingsSchema / normalizeSettings |
Türün ayar alanları (AACP'de form olarak çıkar) |
defaultValue |
Yeni kayıtta varsayılan |
Değer sayısını (cardinality) türünüz değil çağıran kod yönetir; metotlarınız tek değer üzerinde çalışır.