2023-04-18
      
      
        以前的Query可以理解为
	- 文章article有一个字段叫channel
 
	- Channel关联到了taxonomy
 
	-  查询taxonomy中的checkout_status字段值为1
 
// 获取一个Entity类型的Storage
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
// 获取Query
$query = $node_storage->getQuery();
$query->condition('type', 'article');
$query->condition('channel.entity:taxonomy_term.checkout_status', 1);
$ids = $query->execute();
dpm($ids);
 
再来看一个多层链接的
	- 文章article中有一个字段叫channel
 
	- channel关联了taxonomy
 
	- channel中一个字段叫车辆类型:vehicle_type
 
	- vehicle_type又关联了taxonomy
 
	- vehicle_type的名字叫汽车运输的
 
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$query = $node_storage->getQuery();
$query->condition('type', 'article');
$query->condition('channel.entity:taxonomy_term.vehicle_type.entity:taxonomy_term.name', '汽车运输');
$ids = $query->execute();
dpm($ids);
 
        
       
      
          
      
        2023-04-09
      
      
        在使用entity中有些事情比较繁琐, 比如
1. 获取关联的entity
$entity->get('field')->entity->toArray()
这里有一个弊端.如果entity已经被删除了或者entity不存在都会报错.
 
2. 获取多值的target_id, 需要有多步操作.
$ids = [];
if (!$entity->get('field')->isEmpty()) {
  $ids = $entity->get('field')->getValue();
  $ids = array_column($ids, 'target_id');
}
3. 获取多值的广本字段.也需要有多步操作
$values = [];
if (!$entity->get('field')->isEmpty()) {
  $values = $entity->get('field')->getValue();
  $values = array_column($values, 'value');
}
 
4. 获取lis字段的label
$value = '';
if ($entity->get('field')->isEmpty()) {
  $allow_values = $entity->get('field')->getSetting('allowed_values');
  $value = $allow_values[$entity->get('field')->value];
}
以下是一个简单扩展
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
/**
 * entity helper.
 *
 * Class Entity
 */
class Entity {
  /**
   * @var EntityInterface
   */
  private EntityInterface $entity;
  /**
   * init entity.
   *
   * Entity constructor.
   * @param EntityInterface $entity
   */
  public function __construct(EntityInterface $entity) {
    $this->entity = $entity;
  }
  /**
   * get field use custom.
   *
   * @param $field
   * @return FieldItem
   */
  public function get($field):FieldItem {
    return new FieldItem($this->entity, $field);
  }
  /**
   * bundle key
   * node key is type
   * profile key is profile_type
   * @return false|string
   */
  public function bundleKey() {
    return $this->entity->getEntityType()->getKey('bundle');
  }
  /**
   * call entity old functions.
   *
   * @param $name
   * @param $arguments
   * @return false|mixed
   */
  public function __call($name, $arguments)
  {
    if (method_exists($this->entity, $name)) {
      return call_user_func_array(array(
        $this->entity,
        $name
      ), $arguments);
    }
    return false;
  }
  /**
   * call entity old property.
   *
   * @param $name
   * @return FieldItem
   */
  public function __get($name) {
    return new FieldItem($this->entity, $name);
  }
}
/**
 * entity field item.
 * Class FieldItem
 * @package Drupal\dyniva_helper
 */
class FieldItem {
  /**
   * @var FieldItemListInterface
   */
  private FieldItemListInterface $field;
  /**
   * FieldItem constructor.
   * @param EntityInterface $entity
   * @param $field
   */
  public function __construct(EntityInterface $entity, $field) {
    $this->field = $entity->get($field);
  }
  /**
   * set call field old functions.
   *
   * @param $name
   * @param $arguments
   * @return false|mixed
   */
  public function __call($name, $arguments) {
    if (method_exists($this->field, $name)) {
      return call_user_func_array(array(
        $this->field,
        $name
      ), $arguments);
    }
    return false;
  }
  /**
   * set call field old property.
   *
   * @param $name
   * @return mixed
   */
  public function __get($name) {
    return $this->field->{$name};
  }
  public function emptyValue($default = '') {
    if (!$this->field->isEmpty()) {
      return $this->field->getString();
    }
    return $default;
  }
  /**
   * get select field allow values.
   *
   * @return mixed
   */
  public function getAllowValues() {
    return $this->field->getSetting('allowed_values');
  }
  public function selectLabel() {
    $allowvalues = $this->getAllowValues();
    return $allowvalues[$this->field->value] ?? '';
  }
  /**
   * get ref entity.
   *
   * @return false|EntityInterface
   */
  public function ref() {
    if (!$this->field->isEmpty() && $this->field->entity) {
      return new Entity($this->field->entity);
    }
    return false;
  }
  /**
   * get refs entity.
   *
   * @return false|array
   */
  public function refs() {
    if (!$this->field->isEmpty()) {
      $entitys = $this->field->referencedEntities();
      foreach ($entitys as $key => $entity) {
        $entitys[$key] = new Entity($entity);
      }
      return $entitys;
    }
    return [];
  }
  /**
   * get field targets.
   *
   * @return array
   */
  public function targets() {
    if ($this->field->isEmpty()) {
      return [];
    }
    return array_column($this->field->getValue(), 'target_id');
  }
  /**
   * get field values.
   *
   * @return array
   */
  public function values() {
    if ($this->field->isEmpty()) {
      return [];
    }
    return array_column($this->field->getValue(), 'value');
  }
}
 
使用方法
$node = node_load(1);
$entity = new \Drupal\mymodule\Entity($node);
$entity->uid->refs(); 获取多个关联entity
$entity->uid->ref();  获取单个关联的entity
$entity->uid->targets();  获取多个target_id
$entity->get('titlte')->values();  获取多个value
$entity->get('select_field')->selectLabel();  获取list select字段的label. 经常在使用value的时候只会获取Key
 
        
       
      
          
      
        2023-03-18
      
      
        关键词:decorates. 比如覆盖系统里logger.factory服务. 
以前的调用方式是
\Drupal::logger('my_channel')->notice('hello world');
比如我们想修改日志的写入方式, 定义新的服务
services:
  my_log:
    parent: logger.factory
    decorates: logger.factory
    class: Drupal\test_code\Mylog
定义Mylog文件和覆盖getChannel代码. 加一个打印信息
<?php
namespace Drupal\test_code;
use Drupal\Core\Logger\LoggerChannelFactory;
class Mylog extends LoggerChannelFactory{
  public function get($channel)
  {
    dpm('run my loger');
    return parent::get($channel); // TODO: Change the autogenerated stub
  }
}

        
       
      
          
      
        2023-03-17
      
      
        定义服务  
备:创建服务和方法还可以使用: drush gen service:twig-extension
services:
  test_code.twig_extension:
    class: Drupal\test_code\TestCodeTwigExtension
    tags:
      - { name: twig.extension }
定义方法
<?php
namespace Drupal\test_code;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
 * Twig extension.
 */
class TestCodeTwigExtension extends AbstractExtension {
  /**
   * {@inheritdoc}
   */
  public function getFunctions() {
    return [
      new TwigFunction('foo', [$this, 'fooReturn']),
    ];
    // 或者.
    return [
      new TwigFunction('foo', function() {
        return 'xiukun foo'
      })
    ];
  }
  public function fooReturn() {
    return 'xiukun foo';
  }
}
测试方法
可以在任意template.twig.html或者views中使用

 

        
       
      
          
      
        2023-03-15
      
      
        有些方法应该是属于Entity本身,而不应该写在外面.  写起来比较繁琐而且难以维护. 比如
$taskEntity->run();
$taskEntity->setFinish();
当然以上entity是我自定义. 这里的扩展主要是扩展系统或者第三方模块提供的Entity
/**
 * extends entity class.
 *
 * @param array $entity_types
 */
function mymodule_entity_type_alter(array &$entity_types) {
  /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
  $entity_types['node']->setStorageClass('Drupal\mymodule\ArticleNodeStorage');
}
 
自定一个Storage继承系统的Storage
<?php
namespace Drupal\mymodule;
use Drupal\node\NodeStorage;
class ArticleNodeStorage extends NodeStorage {
  public function getEntityClass(?string $bundle = NULL): string
  {
    return ArticleClass::class;
  }
}
 
编写ArticleClass继承原EntityClass
<?php
namespace Drupal\mymodule;
use Drupal\node\Entity\Node;
class ArticleClass extends Node {
  public function toggleElasticsearchRef() {
    if ($this->isPublished()) {
      $this->pushToElasticsearch();
    } else {
      $this->deleteToElasticsearch();
    }
  }
  // 将文章推送到Elasticsearch.
  public function pushToElasticsearch() {
    $params = [
      'id' => $this->id(),
      'title' => $this->label(),
      'body' => $this->get('body')->value,
    ];
    (new \Drupal\mymodule\Repository())->index($params);
  }
  // 从Elasticsearch上删除文章.
  public function deleteToElasticsearch() {
    try {
      (new \Drupal\mymodule\Repository())->delete($this->id());
    } catch (\Exception $e) {
    }
  }
}
 
接下来就可以这样使用了.
$node = \Drupal\node\Entity\Node::load(1);
$node->pushToElasticsearch();
$node->deleteToElasticsearch();
// Entity更新的时候自动检查是否要删除ES索引
function mymodule_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'article') {
    $entity->toggleElasticsearchRef();
  }
}
// Entity删除的时候删除Es索引
function mymodule_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'article') {
    $entity->deleteToElasticsearch();
  }
}
 
        
       
      
          
      
        2023-03-15
      
      
        经常我们自定义select表单可能需要加载大量的entity. 导致性能很低下. 代码如下. 当我的term address有超过1000个时,页面就需要很久了.
$termStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$options = [];
foreach($termStorage->loadByProperties(['vid' => 'address']) as $entity) {
  $options[$entity->id()] = $entity->label();
}
$form['address'] = [
  '#type' => 'select',
  '#title' => $this->t('Address'),
  '#options' => $options,
  '#required' => TRUE,
];
 
这时我们可以使用select2. 使用select的ajax加载选择项, 下载并启用select2. 我们在里面定义了options来自自定义路由: #autocomplete_route_name = test_code.address_autocomplete. 我们这时只需要定义此路由返回部分数据就行. 因为select2有搜索功能. 我们不需要返回全部
<?php
namespace Drupal\test_code\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\Element\EntityAutocomplete;
/**
 * Provides a test_code form.
 */
class SelectForm extends FormBase {
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'test_code_select';
  }
  public static function AddressAutocompleteCallback(&$element) {
    $complete_form = [];
    $element = EntityAutocomplete::processEntityAutocomplete($element, new FormState(), $complete_form);
    $element['#autocomplete_route_name'] = 'test_code.address_autocomplete';
    return $element;
  }
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['address'] = [
      '#type' => 'select2',
      '#select2' => [
        'minimumInputLength' => 0,
      ],
      '#autocomplete' => true,
      '#target_type' => 'taxonomy_term',
      '#autocomplete_route_callback' => self::class . '::AddressAutocompleteCallback',
      '#autocomplete_route_parameters' => [],
      '#title' => $this->t('Address'),
    ];
    return $form;
  }
  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
  }
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }
}
 
定义路由:
test_code.address_autocomplete:
  path: '/test-code/example'
  defaults:
    _title: 'Example'
    _controller: '\Drupal\test_code\Controller\AutocompleteAddress::build'
  requirements:
    _permission: 'access content'
定义controller只返回50条数据。 可以搜索相关更多的数据
<?php
namespace Drupal\test_code\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
 * Returns responses for test_code routes.
 */
class AutocompleteAddress extends ControllerBase {
  /**
   * Builds the response.
   */
  public function build(Request $request) {
    $keyword = $request->query->get('q');
    $termStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
    $query = $termStorage->getQuery();
    // 这里的keyword是select2搜索的关键词.
    if ($keyword) {
      $query->condition('name', "%{$keyword}%", 'LIKE');
    }
    $query->range(0, 50);
    $ids = $query->execute();
    $results = [];
    if ($ids) {
      foreach($termStorage->loadMultiple($ids) as $entity) {
        $results[] = (object)[
          'id' => $entity->id(),
          'text' => $entity->label()
        ];
      }
    }
    return new JsonResponse([
      'results' => $results
    ]);
  }
}
 
        
       
      
          
      
        2023-03-14
      
      
        1. dejavu
https://github.com/appbaseio/dejavu
$ docker run -p 1358:1358 -d appbaseio/dejavu
2. kibana
https://www.elastic.co/cn/kibana/
version: "3.0"
services:
  elasticsearch:
    container_name: elasticsearch
    image: elasticsearch:8.6.2
    restart: always
    environment:
      - xpack.security.enabled=false
      - discovery.type=single-node
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - ./data:/usr/share/elasticsearch/data
      - ./logs:/usr/share/elasticsearch/logs
      - ./plugins:/usr/share/elasticsearch/plugins
    networks:
      - es-net
    ports:
      - 9200:9200
  kibana:
    container_name: kb-container
    image: kibana:8.6.2
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    networks:
      - es-net
    depends_on:
      - elasticsearch
    ports:
      - 5601:5601
networks:
  es-net:
    driver: bridge
 
        
       
      
          
      
        2023-03-06
      
      
        比如碰到class有私有或保护方法,不能直接访问,也不可以直接改代码的情况下可以使用
Class代码
class TestClass {
  protected function protectedList() {
    return ['c', 'd'];
  }
  private function privateList() {
    return ['a', 'b'];
  }
  public function publicList() {
    return ['e', 'f'];
  }
}
$class = new TestClass();
$ref = new \ReflectionMethod($class, 'protectedList');
$ref->setAccessible(TRUE);
$list = $ref->invoke($class);
dpm($list);
输出
Array
(
    [0] => c
    [1] => d
)
 
        
       
      
          
      
        2023-03-06
      
      
        通常一般服务会存在多个参数,不能直接 new classServer. 以下两种方法都可以
\Drupal::classResolver(classServer::class)->myserviceFunction();
\Drupal::service('class_resolver')->getInstanceFromDefinition(classServer::class)->myserviceFunction();
 
        
       
      
          
      
        2023-03-06
      
      
        
$process = Drush::drush($this->siteAliasManager()->getSelf(), 'cache-rebuild');
$process->mustrun();