52. 字段API(中)

字段api的核心为字段对象、字段控件、字段格式化器,在上节中已经强调了字段对象中的字段含义不等于数据库层面的字段(数据表中的列),她是更高一级的抽象,字段对象是一个列表型的类型化数据对象,附属到实体对象作为属性,列表中的每一个条目是一个复合类型的类型化数据对象,可以叫做字段条目对象,字段对象是列表对象决定着字段可以是多值的,这也就是为什么我们在管理后台字段管理中可以设定数量限制的原因,字段条目对象是一个复合类型的类型化数据对象,那么她可以有多个属性,这些属性被定义在字段条目对象的schema方法中(在其返回数组的columns键中指定),这也就是为什么有些字段对象可以储存多个数据的原因,典型的是系统中的body字段,她包括正文、摘要、格式三个属性,这些属性中有一个做为字段对象的主属性,对应的就是复合数据定义对象中的getMainPropertyName()方法返回的名字。通过以上的介绍在脑海中你需要记住两个概念:多值字段、多属性字段,在数据库的储存层面,可以多个字段对象共存于一张表中,这样的表叫做共享表,也可以一个字段一张表,这样的表叫做字段专用表;在表中一个字段对象可以占有多个列以分别储存她的属性数据,事实上我们在管理后台定义的字段都是储存在专用表中,一个字段一张数据表,关于如何储存将在实体储存处理器中详细解释。字段对象的基础实现如下:

字段对象是类型化API数据结构中的列表数据对象,它的默认实现为:

Drupal\Core\Field\FieldItemList

(接口:Drupal\Core\Field\FieldItemListInterface)

其定义对象为:

Drupal\Core\Field\BaseFieldDefinition

(接口:Drupal\Core\Field\FieldDefinitionInterface)

列表中的元素是一个复合数据对象,默认实现为:

Drupal\Core\Field\FieldItemBase

(它是抽象基类,提供给各字段插件继承,其接口:\Drupal\Core\Field\FieldItemInterface)

其定义对象为:

Drupal\Core\Field\TypedData\FieldItemDataDefinition

(接口:Drupal\Core\TypedData\ComplexDataDefinitionInterface,注意这里直接实现了复合数据定义接口)

字段对象的核心内容就是围绕以上几个类展开,各类型字段对象以插件方式提供,它们大多继承自:

Drupal\Core\Field\FieldItemBase

为了管理这些插件系统提供了字段类型插件管理器,见下:

字段类型插件管理器:

容器服务id:plugin.manager.field.field_type

获取方式:\Drupal::service('plugin.manager.field.field_type')

插件定义缓存位置:缓存表cache_discovery 的field_types_plugins条目

插件储存位置:Plugin/Field/FieldType

修改钩子名:field_info

字段类型类需要实现的接口:Drupal\Core\Field\FieldItemInterface

字段类型插件用到了插件分类功能,因此该插件管理器实现了以下接口:

Drupal\Component\Plugin\CategorizingPluginManagerInterface

系统提供以下特征以实现该分类接口:

Drupal\Core\Plugin\CategorizingPluginManagerTrait;

关于插件分类详见本系列的插件主题下节,内容比较简单

字段类型插件同时作为一种类型化数据插件,因此在类型化插件管理器中通过插件派生(衍生)Plugin derivatives机制将字段类型插件映射到了类型化插件,但需要注意在类型化插件管理器中的插件id为:

“field_item:” + 字段类型插件管理器中类型id

也就是"field_item:FIELD_TYPE"形式,不要搞混淆了,插件派生详见本系列的插件主题中节。

字段类型自定义:

定义一个类名以Item结尾的类(这是遵循最佳实践,而非强制的命名方法),放置在模块的Plugin/Field/FieldType目录下,该类必须实现以下接口:

Drupal\Core\Field\FieldItemInterface

可选继承基类Drupal\Core\Field\FieldItemBase,该基类已经实现了以上接口,继承时可不声明接口,用户只需要可选的进行方法覆写和释文定义即可,方法的说明请查看接口,这里列出释文中的一些键的含义:

Category:表示字段类型的分类,在后台添加字段时会看到常规设置、引用、数字、文本四大类分类,如果不设置默认为常规设置

default_widget:字段控件的插件id,用于指示默认控件

default_formatter:字段格式化器的插件id,用于指示默认格式化器

no_ui:布尔值,用于指示该字段能否通过UI来创建

cardinality:设置默认字段允许的数量(输入时可以重复输入多少)

你也可以根据需要增加自己需要用到的键,设置好的释文在hook_field_info_alter()中可以进行修改

更多详细请参见系统已经定义的字段类型。

注:以下的字段控件、字段格式化器和表单API有密切联系,如果您是按本系列发布顺序阅读,那么目前还没有了解到表单api,遇到不清楚的地方不用着急,大致了解即可,以后会多次进行介绍。

字段控件Field widget:

字段控件用于处理字段信息输入时,表单中如何显示这个字段,一个字段在表单中显示时,往往可以有多于一个的方法,比如储存某选择项的字段可以是下拉列表、单选框、复选框等等,参见管理后台某内容类型的“管理表单显示”(如:/admin/structure/types/manage/article/form-display),为此系统提供字段控件来处理该工作,字段控件以插件方式提供,并提供了字段控件管理器。

字段控件插件管理器:

容器服务id:plugin.manager.field.widget

获取方式:\Drupal::service('plugin.manager.field.widget')

插件定义缓存位置:缓存表cache_discovery 的field_widget_types_plugins条目

插件储存位置:Plugin/Field/FieldWidget

修改钩子名:field_widget_info

字段输入控件类需要实现的接口:Drupal\Core\Field\WidgetInterface

字段格式化器field formatter:

在实体显示时,附加在上面的每个字段往往可以有多于一种的方式进行显示,参见管理后台某内容类型的“管理显示”(如:/admin/structure/types/manage/article/display),字段格式化器就是用于处理字段如何显示的,它和字段控件类似,也是以插件方式提供,并提供字段格式化器插件管理器进行管理。

字段格式化器插件管理器:

容器服务id:plugin.manager.field.formatter

获取方式:\Drupal::service('plugin.manager.field.formatter')

插件定义缓存位置:缓存表cache_discovery 的field_formatter_types_plugins条目

插件储存位置:Plugin/Field/FieldFormatter

修改钩子名:field_formatter_info

字段输入控件类需要实现的接口:Drupal\Core\Field\FormatterInterface

字段配置对象:

不是有字段定义对象么?事实上字段配置对象接口就继承了字段定义对象接口,新增了set、add方法,为了解决储存问题同时还继承了配置实体接口(见本系列的配置实体主题),其接口如下:

Drupal\Core\Field\FieldConfigInterface

系统实现了可选继承的基类:

Drupal\Core\Field\FieldConfigBase

字段事件处理:

为响应字段定义的CRUD操作,需要建立侦听器对象,其接口为:

Drupal\Core\Field\FieldDefinitionListenerInterface

默认实现:Drupal\Core\Field\FieldDefinitionListener

Drupal\Core\Field\FieldStorageDefinitionListenerInterface

默认实现:Drupal\Core\Field\FieldStorageDefinitionListener

此外系统提供了事件对象,事件处理机制请查看本系列的事件派发器主题。

(中节完,下节将介绍余下知识,并在相关模块中继续介绍字段api)

作者语:

记得在十年以前,做网站一个人就可以完成,云客就独自完成过很多,那时网站简单,因此有很多全栈工程师,时至今日2017年底,再难听到“全栈”了,原因总结了几点:

一、html5带来了功能上的巨大提升,比如画布操作等,功能的强大往往意味着操作的复杂,需要更多知识

二、趋于双向实时的操作交互网站已然变成了一个应用程序,客户体验变得很重要,对js要求很高,出现了专门的前端程序员、专门的界面设计师

三、终端环境的复杂化,要考虑到移动端、微信端、各类大小分辨率的屏幕等等

四、高并发、大流量带来的冲击

总的来讲就是系统变得越来越复杂、越来越庞大,结果导致社会分工越来越细化,而个体人的精力是有限的,很难全部掌握,这个现象不仅发生在网站行业,在许多方面都有体现,在制造业:比如当年莱特兄弟可以独自造一架飞机,但有人可以独自造一架现代的波音客机么?显然不能,在科学界:有很多我们耳熟能详的开山鼻祖,像牛顿、爱因斯坦等等,他们往往还在多个领域造诣颇深,但现在很难听到有与之等高的人物,在艺术界同样如此,我不相信是人不够聪明,应该是分工越来越细,在整体框架下某一个小领域就能让单个人穷其一生,这种分工细化的进程还在继续。

云客之所以讲到以上这些是源于drupal源代码的研读感受,drupal确实很大,如果你精读源代码,那么需要很长的时间(云客已经超过一年了),读到后面也许已经忘记了前面的细节,drupal整体结构是很简单的,某个细节部分也不难理解,但要想全盘精细掌握是困难的,因为细枝末节太多,这种庞大程度和人有限精力之间的差距越来越大,与时俱进的功能满足系统在不停添加源代码,而人的记忆还是有保鲜期的,这很容易联想到操作系统,目前这个世界上有可能存在一个人全盘了解过linux源代码的吗?我想应该不会有,就连了解过其中的tcp协议的人都很少吧。

鉴于我们面对的这种局面,也许读者应该调整自己的学习方法,不要追求成为事事皆知的drupal超人,而应该追求怎么高效利用自己有限的精力,细节上需要时现查现学,在这个背景下,详尽的文档就变得异常重要,系统是有机组成的,各部分组合在一起,单独看代码很难,当使用者需要了解某部分的内容时,有针对性介绍并照顾到查询者所处局面的文档就是最好的文档,这样的文档应该是模块化的,不假设查询者已经知道相邻知识,或者尽量少假设,在介绍相邻知识时只提供接口介绍,本系列以后将会体现这一点。

那么如何开发出这样的文档呢?既然没有人能全盘掌握,那么就需要集众贡献,当某一个人正在了解某部分细节时,呼吁趁着记忆保鲜期将其详尽的写出来,并标注时间、版本、背景及作者信息,大家都这么做了那么将互利互惠,这就是开源的精神所在。

本书共91小节:

评论 (写第一个评论)