地址格式(address format)

不同国家,有不同的地址格式。不同的地址格式,不同的地址字段顺序,中国是从大到小,美国等拉丁语法一般是从小到大。

解决这个问题,依赖一下东西:

  1. (Query Address Data ) https://chromium-i18n.appspot.com/ssl-address

比如说,Armenia 国家,和美国的,通过配置如下:

  1. 'AM' => [
  2. 'format' => "%givenName %familyName\n%organization\n%addressLine1\n%addressLine2\n%postalCode\n%locality\n%administrativeArea",
  3. 'postal_code_pattern' => '(?:37)?\d{4}',
  4. 'subdivision_depth' => 1,
  5. ],
  6. 'US' => [
  7. 'format' => "%givenName %familyName\n%organization\n%addressLine1\n%addressLine2\n%locality, %administrativeArea %postalCode",
  8. 'required_fields' => [
  9. 'addressLine1', 'locality', 'administrativeArea', 'postalCode',
  10. ],
  11. 'uppercase_fields' => [
  12. 'locality', 'administrativeArea',
  13. ],
  14. 'administrative_area_type' => 'state',
  15. 'postal_code_type' => 'zip',
  16. 'postal_code_pattern' => '(\d{5})(?:[ \-](\d{4}))?',
  17. 'subdivision_depth' => 1,
  18. ],

添加其它地址字段

比如,我需要添加一个Building name 字段。

我们需要创建一个第三方模块,实现一个事件订阅机制,使得我们在地址格式中的“组织”后插入“Building name”字段:

  1. <?php
  2. namespace Drupal\mymodule\EventSubscriber;
  3. use Drupal\address\Event\AddressEvents;
  4. use Drupal\address\Event\AddressFormatEvent;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. /**
  7. * Repurposes additional name field as building name.
  8. */
  9. class BuildingNameEventSubscriber implements EventSubscriberInterface {
  10. public static function getSubscribedEvents() {
  11. $events[AddressEvents::ADDRESS_FORMAT][] = ['onAddressFormat'];
  12. return $events;
  13. }
  14. public function onAddressFormat(AddressFormatEvent $event) {
  15. $definition = $event->getDefinition();
  16. // Place %additionalName after %organization in the format.
  17. $format = $definition['format'];
  18. $format = str_replace('%additionalName', '', $format);
  19. $format = str_replace('%organization', "%organization\n%additionalName", $format);
  20. $definition['format'] = $format;
  21. $event->setDefinition($definition);
  22. }
  23. }

然后,需要将事件订阅类应用在配置(mymodule.services.yml)里:

  1. services:
  2. mymodule.subscriber:
  3. class: Drupal\mymodule\EventSubscriber\BuildingNameEventSubscriber
  4. tags:
  5. - {name: event_subscriber}

然后清一下缓存,就可以看到一个“Middle name”字段出现在地址表单里:

地址 - 图1

然后,我们需要把Middle name改成Building name,并且更改其文本字段大小以匹配公司文本字段,我们可以利用drupal的更名表单Hook,如下:

  1. <?php
  2. use Drupal\Core\Form\FormStateInterface;
  3. function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  4. if (($form_id == 'profile_customer_edit_form') || ($form_id == 'profile_customer_add_form')) {
  5. $form['address']['widget'][0]['address']['#after_build'][] = 'mymodule_customize_address';
  6. }
  7. }
  8. function mymodule_customize_address($element, $form_state) {
  9. $element['additional_name']['#title'] = t('Building name');
  10. $element['additional_name']['#size'] = 60;
  11. return $element;
  12. }

国家支持

Address module里使用了The Commerce Guys Addressing library库,默认支持250个国家地区选择。但是我们平时系统不需要那么多国家,如何处理?

下面举个例子如何指定我需要的国家。首先,需要得到国家的标准双字母代码(standard 2-letter codes),然后,通过事件订阅器的写法,具体事件是AddressEvents::AVAILABLE_COUNTRIES,去实现指定国家,比如我只需要5个国家: Australia, Brazil, Canada, Japan, and the United Kingdom.

  1. <?php
  2. namespace Drupal\mymodule\EventSubscriber;
  3. use Drupal\address\Event\AddressEvents;
  4. use Drupal\address\Event\AvailableCountriesEvent;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. class LimitCountriesEventSubscriber implements EventSubscriberInterface {
  7. public static function getSubscribedEvents() {
  8. $events[AddressEvents::AVAILABLE_COUNTRIES][] = ['onAvailableCountries'];
  9. return $events;
  10. }
  11. public function onAvailableCountries(AvailableCountriesEvent $event) {
  12. $countries = ['AU' => 'AU', 'BR' => 'BR', 'CA' => 'CA', 'GB' => 'GB', 'JP' => 'JP'];
  13. $event->setAvailableCountries($countries);
  14. }
  15. }

然后在自定义模块对应的services.yml引用进来,然后清缓存生效。

如何修改地址分支数据

通过事件订阅器,具体的事件是AddressEvents::SUBDIVISIONS,Address module的代码里有一个测试案例,可以看address/tests/modules/address_test/src/EventSubscriber/GreatBritainEventSubscriber.php.

测试案例里,它想给英国国家添加一个县字段和一个预定义的县列表。

首先,实现AddressEvents::SUBDIVISIONS事件的订阅和腿脚方法onSubdivisions()

  1. public static function getSubscribedEvents() {
  2. $events[AddressEvents::SUBDIVISIONS][] = ['onSubdivisions'];
  3. return $events;
  4. }
  5. public function onSubdivisions(SubdivisionsEvent $event) {
  6. // For administrative areas $parents is an array with just the country code.
  7. // Otherwise it also contains the parent subdivision codes. For example,
  8. // if we were defining cities in California, $parents would be ['US', 'CA'].
  9. $parents = $event->getParents();
  10. if ($event->getParents() != ['GB']) {
  11. return;
  12. }
  13. $definitions = [
  14. 'country_code' => $parents[0],
  15. 'parents' => $parents,
  16. 'subdivisions' => [
  17. // Key by the subdivision code, which is the value that's displayed on
  18. // the formatted address. Could be an abbreviation (e.g 'CA' for
  19. // California) or a full name like below.
  20. // If it's an abbreviation, define a 'name' in the subarray, to be used
  21. // in the address widget dropdown.
  22. 'Anglesey' => [],
  23. // You can optionally define an ISO 3166-2 code for each subdivision.
  24. 'Blaenau Gwent' => [
  25. 'iso_code' => 'GB-BGW',
  26. ],
  27. 'Bridgend' => [],
  28. 'Caerphilly' => [],
  29. 'Cardiff' => [],
  30. 'Carmarthenshire' => [],
  31. 'Ceredigion' => [],
  32. 'Conwy' => [],
  33. 'Denbighshire' => [],
  34. 'Flintshire' => [],
  35. 'Gwynedd' => [],
  36. 'Merthyr Tydfil' => [],
  37. 'Monmouthshire' => [],
  38. 'Neath Port Talbot' => [],
  39. 'Newport' => [],
  40. 'Pembrokeshire' => [],
  41. 'Powys' => [],
  42. 'Rhondda Cynon Taf' => [],
  43. 'Swansea' => [],
  44. 'Tarfaen' => [],
  45. 'Vale of Glamorgan' => [],
  46. 'Wrexham' => [],
  47. ],
  48. ];
  49. $event->setDefinitions($definitions);
  50. }

如何限制地址可选

比如,我只想部分省份可以选,类似下面的方法实现。

使用hook:hook_form_alter()#pre_render回调结合:

  1. function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  2. if (($form_id == 'profile_customer_edit_form') || ($form_id == 'profile_customer_add_form')) {
  3. $form['address']['widget'][0]['address']['#pre_render'][] = 'mymodule_prerender';
  4. }
  5. function mymodule_prerender($element) {
  6. if ($element['country_code']['#default_value'] == 'US') {
  7. $include_states = ['', 'NY', 'NJ', 'PA', 'DE', 'MD', 'DC', 'VA', 'WV'];
  8. $options = array_intersect_key($element['administrative_area']['#options'], array_flip($include_states));
  9. $element['administrative_area']['#options'] = $options;
  10. }
  11. return $element;
  12. }

强制修改地址字段覆盖值

有时候,部分地址字段无法通过drupal的配置去设置,那么可以通过hook来配置,具体的hook是hook_form_alter()

下面是给组织、地区和邮政编码字段属性设置覆盖值。

  1. <?php
  2. use Drupal\Core\Form\FormStateInterface;
  3. function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  4. if (($form_id == 'profile_customer_edit_form') || ($form_id == 'profile_customer_add_form')) {
  5. $form['address']['widget'][0]['address']['#field_overrides'] = [
  6. 'organization' => 'required',
  7. 'locality' => 'optional',
  8. 'postalCode' => 'hidden',
  9. ];
  10. }
  11. }

强制修改地址字段表单属性

在此示例中,我们将对Company字段属性进行以下自定义:将“Company”标签更改为“Organization”。将文本字段大小从60更改为30。*使字段属性成为必填项。

  1. <?php
  2. use Drupal\Core\Form\FormStateInterface;
  3. function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  4. if (($form_id == 'profile_customer_edit_form') || ($form_id == 'profile_customer_add_form')) {
  5. $form['address']['widget'][0]['address']['#after_build'][] = 'mymodule_customize_address';
  6. }
  7. }
  8. function mymodule_customize_address($element, $form_state) {
  9. $element['organization']['#title'] = t('Organization');
  10. $element['organization']['#size'] = 30;
  11. $element['organization']['#required'] = TRUE;
  12. return $element;
  13. }

在本例中,我们将使用两个街道地址字段属性的单独标签来更改默认的“街道地址”标签

  1. function mymodule_customize_address($element, $form_state) {
  2. dpm($element['#field_overrides']);
  3. $element['address_line1']['#title'] = t('Address line 1');
  4. $element['address_line2']['#title'] = t('Address line 2');
  5. $element['address_line2']['#title_display'] = 'before';

地址 - 图2

设置地址初始值

将默认国家设置为澳大利亚,郊区设置为新南威尔士,城市设置为悉尼。

  1. <?php
  2. namespace Drupal\mymodule\EventSubscriber;
  3. use Drupal\address\Event\AddressEvents;
  4. use Drupal\address\Event\InitialValuesEvent;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. class AustraliaDefaultEventSubscriber implements EventSubscriberInterface {
  7. public static function getSubscribedEvents() {
  8. $events[AddressEvents::INITIAL_VALUES][] = ['onInitialValues'];
  9. return $events;
  10. }
  11. public function onInitialValues(InitialValuesEvent $event) {
  12. $initial_values = [
  13. 'country_code' => 'AU',
  14. 'administrative_area' => 'NSW',
  15. 'locality' => 'Sydney',
  16. ];
  17. $event->setInitialValues($initial_values);
  18. }
  19. }

记得在yml服务里引用这个:

  1. services:
  2. mymodule.australia_subscriber:
  3. class: Drupal\mymodule\EventSubscriber\AustraliaDefaultEventSubscriber
  4. tags:
  5. - {name: event_subscriber}

隐藏国家字段

法1:创建一个个自定义地址字段格式化程序插件,扩展了默认的地址格式化程序

下面将创一个插件,将显示除美国以外的所有国家/地区的地址的国家/地区。

假设我们创建好了一个第三方模块,然后再创一个第三方插件,名为AddressHideUSFormatter:

  1. namespace Drupal\mymodule\Plugin\Field\FieldFormatter;
  2. use Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter;
  3. /**
  4. * Plugin implementation of the 'address_us_default' formatter.
  5. *
  6. * @FieldFormatter(
  7. * id = "address_us_default",
  8. * label = @Translation("Hide US"),
  9. * field_types = {
  10. * "address",
  11. * },
  12. * )
  13. */
  14. class AddressHideUSFormatter extends AddressDefaultFormatter {
  15. }

注意了,address_us_default是这个插件的唯一id,可以在注解里看到。重请缓存后,就可以看到我们的插件了:

地址 - 图3

然后,就可以给插件定制行为了,默认,插件有一个默认的方法可以覆写——postRender(),比如说写成这样:

  1. public static function postRender($content, array $element) {
  2. /** @var \CommerceGuys\Addressing\AddressFormat\AddressFormat $address_format */
  3. $address_format = $element['#address_format'];
  4. $locale = $element['#locale'];
  5. // Add the country to the bottom or the top of the format string,
  6. // depending on whether the format is minor-to-major or major-to-minor.
  7. if ($address_format->getCountryCode() == 'US') {
  8. $format_string = $address_format->getFormat();
  9. }
  10. elseif (Locale::matchCandidates($address_format->getLocale(), $locale)) {
  11. $format_string = '%country' . "\n" . $address_format->getLocalFormat();
  12. }
  13. else {
  14. $format_string = $address_format->getFormat() . "\n" . '%country';
  15. }
  16. $replacements = [];
  17. foreach (Element::getVisibleChildren($element) as $key) {
  18. $child = $element[$key];
  19. if (isset($child['#placeholder'])) {
  20. $replacements[$child['#placeholder']] = $child['#value'] ? $child['#markup'] : '';
  21. }
  22. }
  23. $content = self::replacePlaceholders($format_string, $replacements);
  24. $content = nl2br($content, FALSE);
  25. return $content;
  26. }

然后,补充相关依赖:

  1. use CommerceGuys\Addressing\Locale;
  2. use Drupal\Core\Render\Element;

这样,插件就大功告成了。

使用Plain address formatter 定制地址显示

如果想精准控制地址的布局,需要考虑这种方法。

这个方法,是通过twig模板的方法。

address-plain.html.twig

变量如下:

  1. given_name: 名称.
  2. additional_name: 其它名称
  3. family_name: 姓氏
  4. organization: 组织.
  5. address_line1: 第一地址行.
  6. address_line2: 第二地址行.
  7. postal_code: 邮政编码.
  8. sorting_code: 分类代码.
  9. country.code: 国家代码.
  10. country.name: 国家名称.

Subdivision 字段变量:

  1. dependent_locality: 从属地区.
  2. locality: 地区.
  3. administrative_area: 行政区域.

比如,下面需要将城市字段显示出来,如何处理?

地址 - 图4 可以在twig中,应该把上面的{{ locality.code }}改成{{ locality }}.

code和name如何选择?默认以code为高优先级。

  1. dependent_locality.code: 从属地区code.
  2. dependent_locality.name: 从属地区name.
  3. locality.code: 地区code.
  4. locality.name: 地区name.
  5. administrative_area.code: 行政区域 code.
  6. administrative_area.name: 行政区域 name.