FieldTypes, FieldWidgets और FieldFormatters
सारांश
Drupal 8 एक बड़ी बेस क्लास लाइब्रेरी के साथ आता है, जो आपको अपने कंटेंट के साथ काम करने देती है। जब बात कंटेंट एंटिटीज़ (content entities) की आती है, तो आप फील्ड्स (fields) का उपयोग करना चाहते हैं। फील्ड्स को समझना ज़रूरी है, क्योंकि वहीं आपकी एंटिटीज़ अपना डेटा स्टोर करती हैं।
FieldTypes
मुख्य फील्ड टाइप्स:
- boolean
- changed
- comment
- created
- datetime
- daterange
- decimal
- entity_reference
- file
- float
- image
- integer
- language
- link
- list_float
- list_integer
- list_string
- map
- password
- path
- string
- string_long
- telephone
- text
- text_long
- text_with_summary
- timestamp
- uri
- uuid
कस्टम फील्ड टाइप्स
जब भी आप डेटा को इस तरह प्रस्तुत करना चाहें, जैसा Drupal डिफ़ॉल्ट रूप से प्रदान नहीं करता; तो आप अपने डेटा के लिए नया फील्ड टाइप बनाना चाहेंगे।
मान लीजिए, आपके पास एक कंटेंट ऑब्जेक्ट है जिसमें संवेदनशील डेटा है। इस कंटेंट का क्रिएटर कुछ यूज़र्स को पासवर्ड के ज़रिए एक्सेस दे सकता है, और हर यूज़र का पासवर्ड अलग होगा। अगर हम इसे डेटाबेस टेबल्स में देखें, तो आप कुछ इस तरह चाहेंगे:
| entity_id | uid | password | ----------------------------------- | 1 | 1 | 'helloworld' | | 1 | 2 | 'goodbye' |
जैसा कि आप देख सकते हैं, entity id 1 के पास दो अलग-अलग यूज़र्स के लिए दो पासवर्ड हैं। तो Drupal में इसे manually टेबल बनाए बिना कैसे करें? इसके लिए हम नया फील्ड टाइप बनाएँगे।
क्योंकि Drupal फील्ड लॉजिक को Plugin के रूप में इम्प्लीमेंट करता है, हमारे पास हमेशा एक बेस क्लास होती है जिससे हम इनहेरिट कर सकते हैं ताकि वह Drupal में काम करे। नए फील्ड टाइप के लिए आप अपने मॉड्यूल में यह फ़ोल्डर स्ट्रक्चर बनाएँगे:
modules/custom/MODULENAME/src/Plugin/Field/FieldType
यह path लंबा है और थोड़ा झंझट भरा है, लेकिन Drupal इससे सभी अलग-अलग फीचर्स को मॉड्यूल्स में साथ लाना आसान बनाता है।
हमारे उदाहरण में हम EntityUserAccessField.php फाइल बनाएँगे।
namespace Drupal\MODULENAME\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* )
*/
class EntityUserAccessField extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
}
जैसा कि आप देख सकते हैं, फील्ड टाइप कंटेंट एंटिटी जैसा ही लगता है। असल में, दोनों में कोई अंतर नहीं है, लेकिन यह एक और विषय है।
सबसे पहले, हमारे फील्ड टाइप के लिए annotation है:
- @FieldType: यह Drupal लाइब्रेरी से FieldType annotation क्लास को कॉल करता है।
- id: यह हमारे फील्ड टाइप का machine name है, ताकि हम इसे बार-बार उपयोग कर सकें। ध्यान रखें कि PHP या अन्य रिज़र्व नामों को ओवरराइड न करें।
- label: यह machine name का यूज़र-फ्रेंडली अनुवाद हो सकता है।
- description: अगर label पर्याप्त न हो तो आप यहाँ विवरण भी जोड़ सकते हैं।
दूसरे, हमारा क्लास FieldItemBase को एक्सटेंड करता है, जिससे हमें दो methods इम्प्लीमेंट करनी होंगी ताकि हम फील्ड टाइप को सही ढंग से उपयोग कर सकें:
- propertyDefinitions(): यह method कंटेंट ऑब्जेक्ट के baseFieldDefinition जैसा है (लेकिन बिल्कुल वही नहीं)। इससे हम यह तय करते हैं कि इस फील्ड टाइप के लिए एंटिटी फॉर्म्स में कौन सा डेटा दिखेगा।
- schema(): एंटिटीज़ पर यह method deprecated है, लेकिन फील्ड्स में अब भी है। यह method बताता है कि फील्ड का डेटा डेटाबेस में कैसे स्टोर होगा।
क्योंकि यह तुरंत समझ में नहीं आता कि इन methods में क्या लिखें, चलिए इसमें कुछ कोड जोड़ते हैं:
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['uid'] = DataDefinition::create('integer')
->setLabel(t('User ID Reference'))
->setDescription(t('The ID of the referenced user.'))
->setSetting('unsigned', TRUE);
$properties['password'] = DataDefinition::create('string')
->setLabel(t('Password'))
->setDescription(t('A password saved in plain text. That is not safe dude!'));
$properties['created'] = DataDefinition::create('timestamp')
->setLabel(t('Created Time'))
->setDescription(t('The time that the entry was created'));
// ToDo: Add more Properties.
return $properties;
}
इसके अलावा, यूज़र ID को DataReferenceDefinition के ज़रिए भी स्टोर किया जा सकता है, इसे भविष्य में देखा जा सकता है।
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$columns = array(
'uid' => array(
'description' => 'The ID of the referenced user.',
'type' => 'int',
'unsigned' => TRUE,
),
'password' => array(
'description' => 'A plain text password.',
'type' => 'varchar',
'length' => 255,
),
'created' => array(
'description' => 'A timestamp of when this entry has been created.',
'type' => 'int',
),
// ToDo: Add more columns.
);
$schema = array(
'columns' => $columns,
'indexes' => array(),
'foreign keys' => array(),
);
return $schema;
}
schema() Drupal को यह बताने के लिए ज़रूरी है कि हमारा डेटा कैसे सेव होगा। schema columns propertyDefinitions() में डिफाइन की गई properties का subset होना चाहिए।
अब हमारे पास एक नया फील्ड टाइप है। इसमें अभी कोई लॉजिक नहीं है कि डेटा इनपुट कैसे हैंडल किया जाएगा, लेकिन इसे किसी भी कंटेंट एंटिटी पर फील्ड के रूप में लगाया जा सकता है। अगर आप चाहें तो इसे baseField के रूप में भी उपयोग कर सकते हैं:
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Some fields above.
$fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
->setLabel(t('Entity User Access'))
->setDescription(t('Specify passwords for any user that want to see this entity.'))
->setCardinality(-1); // Ensures that you can have more than just one member
// Even more fields below.
return $fields;
}
- BaseFieldDefinition::create(): इसमें आपको फील्ड टाइप का machine name इस्तेमाल करना होगा।
- setCardinality(-1): यह तय करता है कि एक ऑब्जेक्ट में इस फील्ड के कितने values हो सकते हैं। जैसे अगर 2 दिया, तो केवल दो यूज़र ही ऑब्जेक्ट एक्सेस कर पाएँगे। -1 का मतलब है अनलिमिटेड।
FieldWidget
अगर आपके पास कस्टम डेटा है, तो हो सकता है आपको उसका कस्टम UI चाहिए हो। Widgets का उपयोग यह बताने के लिए किया जाता है कि यूज़र फॉर्म्स में उस डेटा को कैसे इनपुट करेगा। उदाहरण:
- अगर फॉर्म में integer चाहिए, लेकिन आप चेकबॉक्स दिखाना चाहते हैं।
- अगर आप अपने डेटा के लिए autocomplete चाहते हैं।
- अगर पासवर्ड इनपुट के लिए स्पेशल GUI चाहिए।
Drupal में Field Widget यहाँ मिलेगा:
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
यह भी लंबा path है। अब तक यह स्पष्ट हो जाना चाहिए कि Drupal क्यों इस स्टाइल में फाइल्स को विभाजित करता है। इससे यह समझना आसान हो जाता है कि कौन सी फाइल कहाँ की है।
हम EntityUserAccessWidget.php बनाएँगे:
namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Plugin implementation of the 'entity_user_access_w' widget.
*
* @FieldWidget(
* id = "entity_user_access_w",
* label = @Translation("Entity User Access - Widget"),
* description = @Translation("Entity User Access - Widget"),
* field_types = {
* "entity_user_access",
* },
* multiple_values = TRUE,
* )
*/
class EntityUserAccessWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// ToDo: Implement this.
}
}
ध्यान दिया आपने? Drupal 8 बार-बार यही कोड पैटर्न इस्तेमाल करता है। Annotation और Base Class, जिन्हें आपको extend करना होता है।
- @FieldWidget: annotation क्लास को परिभाषित करता है।
- id: widget का machine name।
- field_types: उन फील्ड टाइप्स का array, जिन पर यह widget लागू हो सकता है।
- multiple_values: डिफ़ॉल्ट FALSE। TRUE होने पर यह एक से अधिक value इनपुट करने देगा।
अगर आप इस widget को अपने फील्ड टाइप के साथ उपयोग करना चाहते हैं, तो आपको फील्ड टाइप की annotation एडिट करनी होगी:
// ...
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* default_widget = "entity_user_access_w",
* )
*/
// ...
हाँ, हो गया! पर अभी तक कुछ नहीं दिखेगा, क्योंकि हमें formElement() इम्प्लीमेंट करना होगा:
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['userlist'] = array(
'#type' => 'select',
'#title' => t('User'),
'#description' => t('Select group members from the list.'),
'#options' => array(
0 => t('Anonymous'),
1 => t('Admin'),
2 => t('foobar'),
// This should be implemented in a better way!
),
);
$element['passwordlist'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('Select a password for the user'),
);
//setting default value to all fields from above
$childs = Element::children($element);
foreach ($childs as $child) {
$element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
}
return $element;
}
अब अगर आप इस widget के साथ फॉर्म खोलें, तो आपको कम से कम दो इनपुट फील्ड्स दिखेंगी: एक यूज़र चुनने के लिए और दूसरी पासवर्ड के लिए। अगर आप डेटा सेव करने का तरीका जोड़ना चाहते हैं, तो आपको validation methods widget या entity form में जोड़ने होंगे। और जानकारी के लिए देखें Drupal 8 Form API।
इस समय तक आपने कस्टम फील्ड का ज़्यादातर काम पूरा कर लिया है। अगर आप नहीं समझ पा रहे हैं कि क्या हो रहा है, तो कोड ट्राय करें या core मॉड्यूल्स देखें।
FieldFormatters
आखिरी चीज़ जो बचती है, वह है डेटा का प्रदर्शन, जिसे view mode कहते हैं — वैसे जैसे widget फॉर्म मोड होता है। यह तब होता है जब आप ऑब्जेक्ट को इस तरह कॉल करते हैं: yourdrupalpage.com/myentity/1/view
चलिए सीधे कोड पर चलते हैं। modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter के अंतर्गत EntityUserAccessFormatter.php बनाएँ:
namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'entity_user_access_f' formatter.
*
* @FieldFormatter(
* id = "entity_user_access_f",
* label = @Translation("Entity User Access - Formatter"),
* description = @Translation("Entity User Access - Formatter"),
* field_types = {
* "entity_user_access",
* }
* )
*/
class EntityUserAccessFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'uid' => array(
'#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
),
// Add more content
);
}
return $elements;
}
}
यह उदाहरण widget जैसी ही annotation रखता है, इसलिए ज़्यादा समझाने की ज़रूरत नहीं। viewElements() बस हमारे फील्ड टाइप की यूज़र ID से यूज़रनेम दिखाता है। Access logic entity में इम्प्लीमेंट होना चाहिए। इस तरह यह सभी usernames दिखाएगा, जिनके पास entity पर पासवर्ड है।