Điểm:2

Ràng buộc duy nhất trên một số trường thực thể không thành công khi >1 yêu cầu POST jsonapi xảy ra ngay sau một yêu cầu khác

lá cờ cn

Trong một dự án drupal 9 được tách rời hoàn toàn, tôi có một loại thực thể tùy chỉnh và đã thêm một ràng buộc duy nhất cho một số trường như được mô tả đây. Điều này hoạt động tốt và không thể thêm một thực thể thứ hai có cùng giá trị trường. Tuy nhiên, tôi đang sử dụng các yêu cầu POST JSONAPI để tạo các thực thể. Tôi nhận thấy rằng khi đưa ra nhiều yêu cầu POST với các giá trị trường giống hệt nhau ngay sau một yêu cầu khác, phương thức trình xác thực (sử dụng entityTypeManager->getStorage(...)->getQuery(...)->condition(...)->execute() để kiểm tra DB) không trả về các thực thể khác, vì chưa có thực thể trùng lặp nào tồn tại. I E. nó xảy ra nhanh đến mức nhiều thực thể có giá trị giống hệt nhau được tạo ở cùng một dấu thời gian (The tạo giá trị của các thực thể giống hệt nhau)!

Bỏ qua các ràng buộc là nguy hiểm và phải được ngăn chặn.

Tôi có thể làm gì để giải quyết vấn đề này?

Cập nhật Đây là chức năng được gọi bên trong Hạn chếValidator

xác thực hàm công khai($entity, Constraint $constraint)
{
  ...
  nếu (!$this->isUnique($entity))
    $this->context->addViolation($constraint->notUnique);
  ...
}
chức năng riêng làUnique(CustomType $entity) {
  $date = $entity->get('date')->value;
  $type = $entity->bundle();
  $employee = $entity->get('employee')->target_id;
  $query = $this->entityTypeManager->getStorage('custom_type')->getQuery()
    ->điều kiện('trạng thái', 1)
    ->điều kiện('loại', $loại)
    ->điều kiện('nhân viên', $nhân viên)
    ->điều kiện('ngày', $ngày);

  nếu (!is_null($entity->id()))
    $query->condition('id', $entity->id(), '<>');

  $workIds = $query->execute();
  trả về trống($workIds);
}

Tôi rất vui khi tìm thấy bất kỳ sai sót. Cho đến nay mã này hoạt động tốt trong tất cả các trường hợp khác.

Cập nhật Drupal::lock()

Tôi đã triển khai 2 người đăng ký sự kiện để thêm và phát hành \Drupal::lock() như đã đề cập trong các ý kiến. Sử dụng xdebug, tôi có thể xác nhận rằng mã đang chạy, tuy nhiên, khóa dường như không có bất kỳ tác dụng nào. Các tài liệu cho Khóa() là khá hạn chế. Không chắc có gì sai ở đây.

<?php

không gian tên Drupal\custom_entities\EventSubscriber;

sử dụng Symfony\Component\EventDispatcher\EventSubscriberInterface;
sử dụng Symfony\Thành phần\HttpKernel\Sự kiện\RequestEvent;
sử dụng Symfony\Thành phần\HttpKernel\KernelEvents;

lớp JsonApiRequestDBLock triển khai EventSubscriberInterface {

  /**
   * Thêm khóa cho các yêu cầu JSON:API.
   *
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   * Sự kiện cần xử lý.
   */
  hàm công khai onRequest(RequestEvent $event) {
    $request = $event->getRequest();
    if ($request->getRequestFormat() !== 'api_json') {
      trở lại;
    }

    if ($request->attributes->get('_route') === 'jsonapi.custom_type--work.collection.post' &&
      $request->attributes->get('_controller') === 'jsonapi.entity_resource:createIndividual'
    ) {
      $lock = \Drupal::lock();
      $lock->acre('custom_create_lock');
    }

  }

  /**
   * {@inheritdoc}
   */
  hàm tĩnh công khai getSubscribeEvents() {
    $events[KernelEvents::REQUEST][] = ['onRequest'];
    trả lại sự kiện $;
  }

}

và nhả khóa sau khi phản hồi

<?php

không gian tên Drupal\custom_entities\EventSubscriber;

sử dụng Symfony\Component\EventDispatcher\EventSubscriberInterface;
sử dụng Symfony\Component\HttpKernel\Event\ResponseEvent;
sử dụng Symfony\Thành phần\HttpKernel\KernelEvents;

lớp JsonApiResponseDBRelease triển khai EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  hàm tĩnh công khai getSubscribeEvents() {
    $events[KernelEvents::RESPONSE][] = ['onResponse'];
    trả lại sự kiện $;
  }


  /**
   * Phát hành các phản hồi JSON:API.
   *
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
   * Sự kiện cần xử lý.
   */
  hàm công khai onResponse(ResponseEvent $event) {
    $response = $event->getResponse();
    if (strpos($response->headers->get('Content-Type'), 'application/vnd.api+json') === FALSE) {
      trở lại;
    }
    $request = $event->getRequest();
    if ($request->attributes->get('_route') === 'jsonapi.custom_type--work.collection.post' &&
      $request->attributes->get('_controller') === 'jsonapi.entity_resource:createIndividual'
    ) {
      // Thả khóa.
      $lock = \Drupal::lock();
      nếu (!$lock->lockMayBeAvailable('custom_create_lock'))
        $lock->release('custom_create_lock');
    }
  }

}

Điều này đã được thêm vào dịch vụ.yml

  # Người đăng ký sự kiện.
  custom_entities.jsonapi_db_lock.subscriber:
    lớp: Drupal\custom_entities\EventSubscriber\JsonApiRequestDBLock
    thẻ:
      - { tên: event_subscriber }
  custom_entities.jsonapi_response_db_release.subscriber:
    lớp: Drupal\custom_entities\EventSubscriber\JsonApiResponseDBRelease
    thẻ:
      - { tên: event_subscriber }
Jaypan avatar
lá cờ de
Điều đó dường như không nên xảy ra, vì gần như không thể cứu hai thực thể cùng một lúc, một thực thể phải xảy ra trước thực thể kia, vì vậy thực thể thứ hai sẽ bị ràng buộc. Bạn có chắc chắn mã ràng buộc của bạn là chính xác?
lá cờ cn
Tôi đồng ý và tôi khá bối rối vào lúc này. Tôi đã thêm câu lệnh `\Drupal::logger` bên trong `isUnique()`, ghi nhật ký thời gian hiện tại. Nó được gọi nhiều lần vào cùng một giây
apaderno avatar
lá cờ us
Dòng `$this->entityTypeManager->getStorage('custom_type')->getQuery()` thiếu lệnh gọi `accessCheck(FALSE)`, cần thiết để truy vấn bỏ qua bất kỳ quyền truy cập nào của người dùng đã đăng nhập có thể có đối với các thực thể được truy vấn.
4uk4 avatar
lá cờ cn
Tôi không nghĩ rằng đây là vấn đề ở đây. Đó là về các yêu cầu đồng thời. Như @Jaypan đã lưu ý, rất khó có khả năng cùng một dữ liệu trường được gửi hai lần trong vòng một hoặc hai giây. Nhưng nếu điều này có thể xảy ra, bạn cần một số loại cơ chế khóa. Có thể không thể khóa cơ sở dữ liệu trên các bảng liên quan, vì vậy bạn cần một cơ chế khóa độc lập như https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Lock%21LockBackendInterface.php/ nhóm/khóa
lá cờ cn
@ 4k4 Tôi đồng ý, rất khó có khả năng cùng một dữ liệu trường được gửi hai lần và điều đó chưa từng xảy ra với tôi trước đây. Tuy nhiên, nó có thể xảy ra. Tôi cũng đã nghĩ về việc khóa DB và nhờ nhận xét của bạn, tôi sẽ kiểm tra liên kết cơ chế khóa
4uk4 avatar
lá cờ cn
Bạn cần áp dụng khóa trên toàn bộ yêu cầu, không chỉ ràng buộc. Bằng cách ghi đè bộ điều khiển hoặc bằng cách sử dụng người đăng ký sự kiện, KernelEvents::REQUEST để có được khóa, KernelEvents::RESPONSE để giải phóng khóa và KernelEvents::EXCEPTION để xử lý các trường hợp ngoại lệ phát sinh do khóa.
lá cờ cn
@ 4k4 Tôi đã cập nhật câu hỏi cho thấy nỗ lực của tôi để làm việc với ổ khóa. Dường như không có tác dụng. Tôi có thể vẫn còn thiếu smth
4uk4 avatar
lá cờ cn
Khóa của bạn không làm gì cả. Nếu bạn không lấy được khóa, bạn phải đưa ra một ngoại lệ. Theo tùy chọn, bạn có thể đặt vòng lặp chờ trước khi thực hiện việc này.
Điểm:2
lá cờ us

Không nhìn thấy tất cả các mã được sử dụng cho custom_type thực thể, bao gồm mã cho trình xử lý của nó và trả lời lý do tại sao mã không tìm thấy bản sao, có hai "lỗ hổng" mà tôi có thể hình dung có thể có trong mã được hiển thị.

"Lỗi" đầu tiên là truy vấn tìm kiếm các thực thể hiện có hạn chế hơn mức cần thiết. Điều này có nghĩa là nó kiểm tra các trường thực thể không liên quan đến các thực thể trùng lặp, ví dụ:

  • Các trạng thái trường, giả sử đó là trường trạng thái trường được sử dụng bởi các thực thể lõi Drupal
  • Các ngày tháng trường, giả sử nó chứa ngày tạo/dấu thời gian

Thực thể lõi Drupal duy nhất sử dụng xác thực thực thể để tránh các thực thể trùng lặp được tạo là đường dẫn_bí danh thực thể, được thực hiện bởi Bí danh đường dẫn lớp. Thực thể đó có một trạng thái trường, nó hỗ trợ các bản sửa đổi, nhưng nó không có trường để lưu trữ khi được tạo, cũng như không có thực thể chủ sở hữu (giống như các nút).
UniquePathAliasConstraintValidator là trình xác thực ràng buộc thực thể của nó; mã cho UniquePathAliasConstraintValidator::validate() là cái sau.

  $path = $entity->getPath();
  $alias = $entity->getAlias();
  $langcode = $entity->language()->getId();
  $storage = $this->entityTypeManager->getStorage('path_alias');
  $query = $storage->getQuery()
    -> kiểm tra truy cập (SAI)
    ->điều kiện('bí danh', $bí danh, '=')
    ->điều kiện('langcode', $langcode, '=');
  if (!$entity->isNew()) {
    $query->condition('id', $entity->id(), '<>');
  }
  nếu ($đường dẫn) {
    $query->condition('path', $path, '<>');
  }
  if ($result = $query->range(0, 1)->execute()) {
    $current_alias_id = đặt lại($result);
    $current_alias = $storage->load($current_alias_id);
    if ($current_alias->getAlias() !== $alias) {
      $this->context->buildViolation($constraint->differentCapitalizationMessage, ['%alias' => $alias, '%stored_alias' => $current_alias->getAlias()])
        ->addViolation();
    }
    khác {
      $this->context->buildViolation($constraint->message, ['%alias' => $alias])
        ->addViolation();
    }
  }
}

Sử dụng mã đó làm ví dụ và chỉ sử dụng mã kiểm tra nghiêm ngặt các bản sao, trong trường hợp của bạn, tôi sẽ sử dụng mã sau. (Tôi chỉ hiển thị mã cho là duy nhất().)

chức năng riêng làUnique(CustomType $entity) {
  $date = $entity->get('date')->value;
  $type = $entity->bundle();
  $employee = $entity->get('employee')->target_id;
  $query = $this->entityTypeManager->getStorage('custom_type')
    ->getQuery()
    -> kiểm tra truy cập (SAI)
    ->điều kiện('loại', $loại)
    ->điều kiện('nhân viên', $nhân viên)
    ->điều kiện('ngày', $ngày);

  if (!$entity->isNew()) {
    $query->condition('id', $entity->id(), '<>');
  }

  $result = $query->range(0, 1)->execute();
  trả về trống($kết quả);
}

Tôi đã thêm cuộc gọi vào accessCheck(FALSE) bởi vì không nhìn thấy mã được trình xử lý truy cập thực thể sử dụng và không biết liệu thực thể đó có thực thể chủ sở hữu hay không, tôi không thể loại trừ việc triển khai là duy nhất() không tìm thấy các bản sao vì người dùng hiện đang đăng nhập không có quyền truy cập vào các bản sao (hoặc bất kỳ thực thể nào thuộc loại đó). không cần gọi accessCheck(FALSE), truy vấn sẽ trả về các thực thể mà người dùng hiện đang đăng nhập có quyền truy cập.
(Cuộc gọi bị thiếu đến accessCheck(FALSE) là "lỗ hổng" khác, có thể, tôi có thể thấy trong mã được hiển thị.)

lá cờ cn
Cảm ơn! Tôi đã thử mã này, chơi xung quanh và kiểm tra nhiều lần. Tuy nhiên, vấn đề thực tế vẫn còn, với 2 yêu cầu đồng thời, trình xác thực của tôi không ngăn được các thực thể trùng lặp. Tôi đang tìm hiểu sâu hơn và luôn cập nhật vấn đề này.
apaderno avatar
lá cờ us
Trường *date* có được sử dụng bởi mã của bạn giống như trường *created* được sử dụng cho các nút không? Nếu đúng như vậy, tôi sẽ không tìm kiếm các thực thể hiện có có cùng giá trị cho *ngày*.
lá cờ cn
không chắc chính xác ý của bạn là gì nhưng ngày rất quan trọng để kiểm tra tính duy nhất
apaderno avatar
lá cờ us
Ý tôi là nếu trường đó chứa thời điểm thực thể được tạo, thì tôi sẽ không đưa nó vào truy vấn để tìm các mục trùng lặp. Rõ ràng, nếu thực thể dành cho các sự kiện, chẳng hạn và *date* là ngày đã lên lịch cho sự kiện, thì tôi sẽ đưa nó vào.

Đăng câu trả lời

Hầu hết mọi người không hiểu rằng việc đặt nhiều câu hỏi sẽ mở ra cơ hội học hỏi và cải thiện mối quan hệ giữa các cá nhân. Ví dụ, trong các nghiên cứu của Alison, mặc dù mọi người có thể nhớ chính xác có bao nhiêu câu hỏi đã được đặt ra trong các cuộc trò chuyện của họ, nhưng họ không trực giác nhận ra mối liên hệ giữa câu hỏi và sự yêu thích. Qua bốn nghiên cứu, trong đó những người tham gia tự tham gia vào các cuộc trò chuyện hoặc đọc bản ghi lại các cuộc trò chuyện của người khác, mọi người có xu hướng không nhận ra rằng việc đặt câu hỏi sẽ ảnh hưởng—hoặc đã ảnh hưởng—mức độ thân thiện giữa những người đối thoại.