在sites/default/settings.php中加入
$config['system.logging']['error_level'] = 'verbose';
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('error_reporting', E_ALL);
$settings['http_client_config']['verify'] = false;
在sites/default/settings.php中加入
$config['system.logging']['error_level'] = 'verbose';
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('error_reporting', E_ALL);
$settings['http_client_config']['verify'] = false;
Drupal的序列化和反序列化用的是 symfony/serializer
服务
$serializer = \Drupal::service("serializer");
Node序列化成xml.
$node = node_load(1);
$serializer = \Drupal::service("serializer");
$xml = $serializer->serialize($node, 'xml');
// 再解析回来
$deserializedData = $serializer->deserialize($xml, $node::class, 'xml');
dpm($deserializedData);
解析XML为数组
$data = <<<EOF
<?xml version="1.0"?>
<response><userid>1</userid><username>xiukunabcd</username></response>
EOF;
$serializer = \Drupal::service("serializer");
$deserializedData = $serializer->decode($data, 'xml');
dpm($deserializedData);
定义csv解析和反解析, 先定义一个服务:
services:
mymodule.encoder.csv:
class: Drupal\mymodule\Encoder\CsvEncoder
tags:
- { name: encoder, format: csv }
目录在src/Encoder/CsvEncoder.php
<?php
namespace Drupal\rest_output\Encoder;
class CsvEncoder extends \Symfony\Component\Serializer\Encoder\CsvEncoder {
protected $format = 'csv';
public function decode(string $data, string $format, array $context = []): mixed
{
$context[CsvEncoder::AS_COLLECTION_KEY] = false;
return parent::decode($data, $format, $context); // TODO: Change the autogenerated stub
}
}
测试
$node = node_load(1);
$serializer = \Drupal::service("serializer");
$xml = $serializer->serialize($node, 'csv');
$deserializedData = $serializer->deserialize($xml, $node::class, 'csv');
dpm($deserializedData);
在目录中vendor/symfony/serializer/Encoder还可能看到有yaml的解析。 可以使用上面代码复写
关键词: autowire: true
更新日志: https://www.drupal.org/node/3218156
在默认的服务器中如果想要传入参数都需要在服务中使用arguments. autowire可以自动加载服务
比如以下:
services:
Drupal\mymodule\Helper:
autowire: true
服务在初始化的时候会有参数EntityTypeManagerInterface.
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityTypeManagerInterface;
final class Helper {
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager
) {}
public function getHello() {
return 'hello';
}
}
但是这并不会生效。自动装载装载的是当前服务中有EntityTypeManagerInterface这个服务才会自动装载。现在来改一个原来的services.yml
services:
Drupal\mymodule\Helper:
autowire: true
Drupal\Core\Entity\EntityTypeManagerInterface: '@entity_type.manager'
function MYMODULE_views_pre_render(&$view) {
if ($view->name == 'view_myviewname') {
// Set the view title.
$view->setTitle('New title');
$result = $view->result;
// Set the ro title
foreach ($result as $i => $row) {
$view->_entity->set('title', 'New titile for row');
$row->_entity->set('field_name', "new value");
}
}
}
function hook_preprocess_views_view_field(&$variables) {
$view = $variables['view'];
$field = $variables['field'];
$row = $variables['row'];
if ($view->storage->id() == 'view_name' && $field->field == 'field_name') {
$variables['output'] = 'News output';
// Example of inline styling
$value = $field->getValue($row);
$markup = [
'#type' => 'inline_template',
'#template' => '{{ yourvar }} {{ yourhtml | raw }}',
'#context' => [
'yourvar' => $value,
'yourhtml' => '<span style="color:red;">Your HTML</span>',
],
];
$variables['output'] = $markup;
}
}
logger.factory:
class: Drupal\Core\Logger\LoggerChannelFactory
arguments: ['@request_stack', '@current_user']
tags:
- { name: service_collector, tag: logger, call: addLogger }
以上代码的定义中name是说这个服务会进行服务收集. 收集的服务名为 logger. 收集的服务将会调用Drupal\Core\Logger\LoggerChannelFactory::addLogger这个函数
https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/service-tags
$header = []
foreach ($result as $ip) {
$row = [];
$links = [];
$links['delete'] = [
'title' => $this->t('Delete'),
'url' => Url::fromRoute('myrouter'),
];
$row[] = [
'data' => [
'#type' => 'operations',
'#links' => $links,
],
];
$rows[] = $row;
}
[
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
]
1.增加索引到自定义Entity
下面我们来使用代码定义一个entity.
下面定义了一个Request的Entity: mymodule/src/Entity/Request.php
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
*
* @ContentEntityType(
* id = "requests",
* label = @Translation("Requests"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
* "views_data" = "Drupal\views\EntityViewsData",
* "form" = {
* "default" = "Drupal\Core\Entity\ContentEntityForm",
* "add" = "Drupal\Core\Entity\ContentEntityForm",
* "edit" = "Drupal\Core\Entity\ContentEntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
* },
* "access" = "Drupal\Core\Entity\EntityAccessControlHandler",
* },
* admin_permission = "administer requests",
* base_table = "requests",
* translatable = FALSE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid"
* },
* )
*/
class Requests extends ContentEntityBase {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['method'] = BaseFieldDefinition::create('string')
->setLabel(t('method'));
$fields['url'] = BaseFieldDefinition::create('string')
->setLabel(t('url'));
return $fields;
}
}
添加4条数据作为测试
$storage = \Drupal::entityTypeManager()->getStorage('requests');
$storage->create([
'method' => 'POST',
'url' => 'https://www.a.com'
])->save();
$storage->create([
'method' => 'GET',
'url' => 'https://www.b.com'
])->save();
$storage->create([
'method' => 'PUT',
'url' => 'https://www.c.com'
])->save();
$storage->create([
'method' => 'DELETE',
'url' => 'https://www.d.com'
])->save();
打开PHPMyAdmin查看会看到有4条数据.
现在执行一个自定义SQL查询数据
SELECT * FROM `requests` WHERE method="POST"
这里会很正常的显示一条.
然后我们这边使用EXPLAIN来查看SQL查询细节
我们会发现我们想要的其实是其中一条数据. 但是SELECT他进行了全表扫描.
其主要原因是因为我没有对字段增加索引, 如果你要对表进行搜索你必须对其增加索引. 不增加就会全表扫描.
我们需要修改一下entity定义. 增加一个storage_schema
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
* "storage_schema" = "Drupal\mymodule\RequestsStorageSchema",
接着我们定义 mymodule/src/RequestsStorageSchema.php
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
/**
* Defines the Requests schema handler.
*/
class RequestsStorageSchema extends SqlContentEntityStorageSchema {
/**
* {@inheritdoc}
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$schema = parent::getEntitySchema($entity_type, $reset);
if ($data_table = $this->storage->getBaseTable()) {
$schema[$data_table]['indexes'] += [
'requests__url' => ['url'],
'requests__method' => ['method'],
];
}
return $schema;
}
}
接着我们只运行: drush entity-updates, 此功能: https://www.drupal.org/project/devel_entity_updates
我们再运行上面的sql进行测试:
EXPLAIN SELECT * FROM `requests` WHERE method="POST"
这时候可以看到这里的rows已经变成了1
2.对Node Body字段和自定义字段增加索引
在系统的Node中. 我们经常需要对body也进行搜索. 但是body字段并没有索引所以就可能导致当文章很多的时候搜索会很漫.
自定义字段也是一样. 不会有索引. 我会在后台创建一个field_myname的字段
我们查询1条数据. 实际上他也是进行了全表扫描.
这里直接使用hook_entity_field_storage_info_alter
function mymodule_entity_field_storage_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type) {
if ($entity_type->id() == 'node' && !empty($fields['body'])) {
$fields['body']->setIndexes(['value' => ['value']]);
$fields['field_myname']->setIndexes(['value' => ['value']]);
}
}
然后运行drush entity-updates
接着测试sql
暂时没有模块可以提供可以增加索引的功能. 有一个field_index的模块但是并不支持drupal10, 还有一个模块可以提示views中搜索字段没有增加索引views_index_hint
首先定义一个事件服务.
mymodule.services.yml
services
mymodule.event_subscriber:
class: Drupal\mymodule\EventSubscriber\MariadbProfileSubscriber
tags:
- { name: event_subscriber }
定义服务内容
<?php
declare(strict_types=1);
namespace Drupal\mymodule\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* @todo Add description for this subscriber.
*/
final class MariadbProfileSubscriber implements EventSubscriberInterface {
/**
* Kernel request event handler.
*/
public function onKernelRequest(RequestEvent $event): void {
// 开启profiles
\Drupal::database()->query('set profiling=1')->execute();
}
public function onResponse(ResponseEvent $event):void {
if ($event->isMainRequest()) {
$is_debug_more = true;
$strings = '';
// 通过show profiles查看页面所有query的相应速度.
$query_list = \Drupal::database()->query('show profiles;')->fetchAll();
uasort($query_list, function($a, $b) {
return ($a->Duration < $b->Duration) ? 1 : -1;
});
foreach ($query_list as $item) {
$strings .= <<<EOF
QueryID = {$item->Query_ID}
Query = {$item->Query}
Duration = {$item->Duration}
EOF;
if ($is_debug_more) {
// 通过select INFORMATION_SCHEMA.PROFILING表来查看单条sql为什么会漫.
// 设置query id.
\Drupal::database()->query('set @query_id=' . $item->Query_ID);
$query = <<<EOF
SELECT STATE, SUM(DURATION) AS Total_R,
ROUND(
100* SUM(DURATION)/
(SELECT SUM(DURATION) FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID = @query_id), 2) AS Pct_R,
COUNT(*) AS Calls,
SUM(DURATION)/COUNT(*) AS "R/Ca11"
FROM INFORMATION_SCHEMA.PROFILING
WHERE QUERY_ID = @query_id
GROUP BY STATE
ORDER BY Total_R DESC;
EOF;
$res = \Drupal::database()->query($query)->fetchAll();
foreach ($res as $sub_item) {
$sub_item = (array)$sub_item;
$strings .= <<<EOF
STATE:{$sub_item["STATE"]}, Total_R:{$sub_item["Total_R"]}, Pct_R:{$sub_item["Pct_R"]}, Calls:{$sub_item["Calls"]}, R/Ca11:{$sub_item['R/Ca11']}
EOF;
$strings .= "\r\n";
}
}
$strings .= "\r\n\r\n";
}
\Drupal::logger('debug_sql')->notice("<pre>{$strings}</pre>");
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
KernelEvents::REQUEST => ['onKernelRequest'],
KernelEvents::RESPONSE => ['onResponse']
];
}
}
ECA模块全名(事件 - 条件 - 行动)有点像Rules。 但是他更强大。只需要拖拖拽拽就可以完成。
这边主要介绍一下 ECA+BPMN.iO模块.
像上面的图是,当执行cron的时候。 切换到管理员(由于cron是匿名用户执行的). 获取views查询数据,这里做了一个活动报名的view. 将对即将要开始的活动提前一天对所有报名的用户发送邮件。 views只是用来查出所有需要发送邮件的用户. 获取到数据以后将数据保存成变量。后面循环执行下面那一个新的自定义事件. 发送邮件.
这里有更详细的视频介绍:https://www.youtube.com/watch?v=foL8V6MCXrM
eca.tar
_core:
default_config_hash: Q1nMi90W6YQxKzZAgJQw7Ag9U4JrsEUwkomF0lhvbIM
langcode: zh-hans
field_prefix: field_
修改为
_core:
default_config_hash: Q1nMi90W6YQxKzZAgJQw7Ag9U4JrsEUwkomF0lhvbIM
langcode: zh-hans
field_prefix: ''