2025-04-14
//建造者模式,目的是消除其它对象复杂的创建过程
/* 描述一个用户的类,包含用户姓名,年龄,金钱 */
class UserInfo {
    protected $userName = ''; 
    protected $userAge = '';
    protected $userMoney = '';
    public function setUserName($userName) {
        $this->userName = $userName;
    }
    public function setUserAge($userAge) {
        $this->userAge = $userAge;
    }
    public function setUserMoney($userMoney) {
        $this->userMoney = $userMoney;
    }
    public function getPeople() {
        echo "这个人的姓名是:" . $this->setUserName . ',年龄是:' . $this->userAge . ', 金钱:' . $this->userMoney;
    }
}
/* 实例化,并且创建这个用户的时候,是很痛苦的,需要设置用户名,年龄和金钱*/
$peopleInfo = array(
    'userName' =  'initphp',
    'userAge' =  28,
    'userMoney' =  '100元'
    );
$UserInfo = new UserInfo;
//下面需要一步步的设置用户信息,才能得到用户详细信息,过程纠结而痛苦
$UserInfo->setUserName($peopleInfo['userName']); 
$UserInfo->setUserAge($peopleInfo['userAge']);
$UserInfo->setUserMoney($peopleInfo['userMoney']);
$UserInfo->getPeople();

 

//UserInfoBuilder 用户信息建造者类,将UserInfo的创建过程封装掉,开发者使用起来心情舒畅
<?php
//建造者模式,目的是消除其它对象复杂的创建过程
include("UserInfo.php");
class UserInfoBuilder {
    protected $obj;
    public function __construct() {
        $this->obj = new UserInfo;
    }
    public function buildPeople($peopleInfo) {
        $this->obj->setUserName($peopleInfo['userName']);
        $this->obj->setUserAge($peopleInfo['userAge']);
        $this->obj->setUserMoney($peopleInfo['userMoney']);
    } 
    public function getPeople() {
        $this->obj->getPeople();
    }
}
/* 创建过程被封装了,用户使用简单了 */
$peopleInfo = array(
    'userName' =  'initphp',
    'userAge' =  28,
    'userMoney' =  '100元'
    );
$UserInfoBuilder = new UserInfoBuilder;
$UserInfoBuilder->buildPeople($peopleInfo); //直接一个build
$UserInfoBuilder->getPeople();

文章来源于: https://cloud.tencent.com/developer/article/1723155

 

标签: 设计模式
2025-04-14

适配器模式就是保留现有类所提供的功能和服务 在不改变原有系统的基础上提供新的接口服务. 比如苹果笔记本并不支持直接插入网线. 而想要插入网线就需要买一个适配器. 这个适配器可以只支持插入网线也可以是功能强大的支持各种插口

使用场景如下:

  1. 一直调用系统的一个服务. 随着服务升级里面的一些函数改调用方法或者直接移掉了
  2. 你想使用一个已经存在的类,而它的接口不符合你的需求。
  3. 你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好

 

 

标签: 设计模式
2025-04-14

需求.

  1. 用户登陆成功根据用户的专业给用户推送产品.
  2. 用户登陆成功给用户发送一封邮件
  3. 用户登陆成功判断后一次登陆时间如果超过1个月告诉通知营销部门
  4. 用户登陆成功如果角色是超级管理员自动跳转到 /admin/contents 页面
  5. 用户登陆成功如果角色是网站管理员自动跳转到 /manage页面

 

先定义一个模块来完成我们的需求. 模块名为.

普通的做法

use Drupal\user\UserInterface;

/**
 * Implements hook_user_login().
 *
 * @param UserInterface $account
 * @throws \Drupal\Core\Entity\EntityMalformedException
 */
function observer_user_login(UserInterface $account) {

  // #############  用户登陆成功根据用户的专业给用户推送产品. ########## //
  switch ($account->get('field_profession')->value) {
    case 'A':
      //一大堆逻辑
      break;
    case 'B':
      // 一大堆逻辑
      break;
    case 'C':
      // 一大堆逻辑
      break;
    default:
      // 一大堆逻辑
      break;
  }

  // #############  用户登陆成功给用户发送一封邮件. ########## //
  // 发送邮件逻辑

  // #############  用户登陆成功判断后一次登陆时间如果超过1个月告诉通知营销部门. ########## //
  if (time() - $account->get('access')->value > 3000) {
     // 通知营销部门逻辑
  }

  // #############  用户登陆成功如果角色是超级管理员自动跳转到 /admin/contents 页面. ########## //
  if ($account->hasRole('administrator')) {
    // 跳转逻辑
  }

  // #############  用户登陆成功如果角色是网站管理员自动跳转到 /manage页面. ########## //
  if ($account->hasRole('webmaster')) {
    // 跳转逻辑
  }
}

这里的代码越后面会越乱,如果到时再增加一个需求登陆成功要做某事。可能又是一大堆逻辑.

现在改用普通的PHP观察者模式分离代码

PHP观察者模式

定义: src/Observer.php

<?php

namespace Drupal\observer;

use Drupal\user\UserInterface;

/**
 * 创建主题用来注册所有的观察者.
 *
 * Class Observer
 */
class Observer implements \SplSubject {

  private array $observers = [];

  private UserInterface $account;

  public function setAccount(UserInterface $account) {
    $this->account = $account;
  }

  public function getAccount() {
    return $this->account;
  }

  /**
   * 注册观察者.
   *
   * @param \SplObserver $observer
   */
  public function attach(\SplObserver $observer):void {
    $this->observers[] = $observer;
  }

  /**
   * 移掉观察者.
   *
   * @param \SplObserver $observer
   */
  public function detach(\SplObserver $observer):void {
    if ($key = array_search($observer,$this->observers, true)){
      unset($this->observers[$key]);
    }
  }

  /**
   * 通知观察者.
   */
  public function notify():void {
    foreach ($this->observers as $value) {
      $value->update($this);
    }
  }
}

 

分别定义功能代码:
src/Observers/Notice.php
src/Observers/NoticeMarkting.php
src/Observers/PushProduct.php
src/Observers/RedAdmin.php
src/Observers/RedWebmaster.php
代码都类似如下。只是类名不一样.

 
 
 
<?php
namespace Drupal\observer\Observers;

use Drupal\observer\Observer;
use SplSubject;

class Notice implements \SplObserver {

  public function update(Observer|SplSubject $subject):void {
    $account = $subject->getAccount();
    \Drupal::logger('observer')->notice('用户登陆成功给用户发送一封邮件: ' . $account->label());
  }
}

 

然后修改hook

<?php

use Drupal\observer\Observers\Notice;
use Drupal\observer\Observers\NoticeMarkting;
use Drupal\observer\Observers\PushProduct;
use Drupal\observer\Observers\RedAdmin;
use Drupal\observer\Observers\RedWebmaster;

use Drupal\user\UserInterface;
use Drupal\observer\Observer;

/**
 * Implements hook_user_login().
 *
 * @param UserInterface $account
 */
function observer_user_login(UserInterface $account) {
  $observer = new Observer();
  $observer->setAccount($account);

  $observer->attach(new Notice());
  $observer->attach(new NoticeMarkting());
  $observer->attach(new PushProduct());
  $observer->attach(new RedAdmin());
  $observer->attach(new RedWebmaster());

  $observer->notify();
}

 
Drupal的event和hook就很类似观察者模式. 现在可以使用event来实现这段代码
 


Event实现

这里定义一个新的模块: obevent

先自定义一个用户登陆的事件: src/Event/UserLoginEvent.php

<?php
namespace Drupal\obevent\Event;

use Drupal\Component\EventDispatcher\Event;
use Drupal\user\UserInterface;

class UserLoginEvent extends Event {

  const EVENT_NAME = 'custom_event_user_login';

  /**
   * @var \Drupal\user\UserInterface
   */
  public UserInterface $account;

  public function getAccount() {
    return $this->account;
  }

  public function setAccount($account) {
    $this->account = $account;
  }

  public function __construct(UserInterface $account) {
    $this->setAccount($account);
  }
}

 

定义功能实现的服务: obevent.services.yml

services:
  obevent.user_login_notice_event_subscriber:
    class: Drupal\obevent\EventSubscriber\LoginNoticeSubscriber
    tags:
      - { name: event_subscriber }
  obevent.user_login_notice_markting_event_subscriber:
    class: Drupal\obevent\EventSubscriber\LoginNoticeMarktingSubscriber
    tags:
      - { name: event_subscriber }
  obevent.user_login_push_product_event_subscriber:
    class: Drupal\obevent\EventSubscriber\LoginPushProductSubscriber
    tags:
      - { name: event_subscriber }

  obevent.user_login_red_admin_subscriber:
    class: Drupal\obevent\EventSubscriber\LoginRedAdminSubscriber
    tags:
      - { name: event_subscriber }

  obevent.user_login_red_webmater_subscriber:
    class: Drupal\obevent\EventSubscriber\LoginRedWebmasterSubscriber
    tags:
      - { name: event_subscriber }
 

定义功能实现代码:

src/EventSubscriber/LoginNoticeMarktingSubscriber.php
src/EventSubscriber/LoginNoticeSubscriber.php
src/EventSubscriber/LoginPushProductSubscriber.php
src/EventSubscriber/LoginRedAdminSubscriber.php
src/EventSubscriber/LoginRedWebmasterSubscriber.php

功能类似下面这样. 只是类名不一样

<?php

declare(strict_types=1);

namespace Drupal\obevent\EventSubscriber;

use Drupal\obevent\Event\UserLoginEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class LoginNoticeSubscriber implements EventSubscriberInterface {

  /**
   * Kernel request event handler.
   */
  public function onUserLogin(UserLoginEvent $event): void {
    $account = $event->getAccount();
    \Drupal::logger('observer')->notice('用户登陆成功给用户发送一封邮件: ' . $account->label());
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      UserLoginEvent::EVENT_NAME => ['onUserLogin'],
    ];
  }

}
 
 
修改obevent.module文件.
<?php

use Drupal\obevent\Event\UserLoginEvent;
use Drupal\user\UserInterface;

/**
 * Implements hook_user_login().
 *
 * @param UserInterface $account
 */
function obevent_user_login(UserInterface $account) {
  $event = new UserLoginEvent($account);
  \Drupal::service('event_dispatcher')->dispatch($event, UserLoginEvent::EVENT_NAME);
}
标签: PHP 设计模式
2025-04-14

关键词: 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'

 

 

标签: Drupal
2025-04-14

1.hook_views_pre_render

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");    
    }
  }
}

2. function hook_preprocess_views_view_field(&$variables)

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;
  }
}

 

 

 

标签: Drupal
2025-04-14
  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

 

标签: Drupal
2025-04-14
$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,
]

 

标签: Drupal
2025-04-14

在以前类初始化赋值是需要这样的

class MyClass {
  private $a;
  private $b;
  public function __construct(string $a, string $b) {
    $this->a = $a;
    $this->b = $b;

    var_dump($this->a);
  }
}

PHP8

class Abcd {

  public function __construct(private string $a, public private $b) {
    var_dump($this->a);
  }

}

或者如下:

class Abcd {

  public function __construct(public readonly string $a, public readonly string $b) {
    var_dump($this->a);
  }

}

$abcd = new Abcd('a', 'b');
var_dump($abcd->a);

原文: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion

 

标签: PHP
2025-04-14

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

 

标签: Drupal mysql
2025-04-14

首先定义一个事件服务.

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']
    ];
  }
}

标签: Drupal mysql