Score:2

ข้อจำกัดเฉพาะในฟิลด์เอนทิตีหลายฟิลด์ล้มเหลวเมื่อ >1 คำขอ jsonapi POST เกิดขึ้นหลังจากนั้นอีกรายการหนึ่ง

ธง cn

ในโครงการ drupal 9 ที่แยกส่วนทั้งหมดฉันมีประเภทเอนทิตีที่กำหนดเองและเพิ่มข้อ จำกัด เฉพาะสำหรับหลาย ๆ ฟิลด์ตามที่อธิบายไว้ ที่นี่. วิธีนี้ใช้งานได้ดีและไม่สามารถเพิ่มเอนทิตีที่สองที่มีค่าฟิลด์เดียวกันได้ อย่างไรก็ตาม ฉันใช้คำขอ JSONAPI POST เพื่อสร้างเอนทิตี ฉันสังเกตเห็นว่าเมื่อออกคำขอ POST หลายรายการด้วยค่าฟิลด์เดียวกันที่ตรงกันหลังจากนั้น วิธีการตรวจสอบความถูกต้อง (โดยใช้ไฟล์ entityTypeManager->getStorage(...)->getQuery(...)->เงื่อนไข(...)->ดำเนินการ() เพื่อตรวจสอบฐานข้อมูล) ไม่ส่งคืนเอนทิตีอื่นเนื่องจากยังไม่มีเอนทิตีที่ซ้ำกัน เช่น. มันเกิดขึ้นอย่างรวดเร็วจนมีการสร้างเอนทิตีที่มีค่าเหมือนกันหลายรายการในเวลาประทับเวลาเดียวกัน (The สร้าง ค่าของเอนทิตีเหมือนกัน)!

การข้ามข้อจำกัดเป็นสิ่งที่อันตรายและต้องป้องกัน

ฉันจะทำอย่างไรเพื่อแก้ปัญหานี้

อัปเดต นี่คือฟังก์ชันที่ถูกเรียกใช้ภายใน ConstraintValidator

ตรวจสอบฟังก์ชั่นสาธารณะ ($ เอนทิตี, ข้อ จำกัด $ ข้อ จำกัด )
{
  ...
  ถ้า (!$this->isUnique($entity))
    $this->context->addViolation($constraint->notUnique);
  ...
}
ฟังก์ชันส่วนตัว isUnique (CustomType $entity) {
  $date = $entity->get('date')->value;
  $type = $entity->bundle();
  $employee = $entity->get('employee')->target_id;
  $query = $this->entityTypeManager->getStorage('custom_type')->getQuery()
    ->เงื่อนไข ('สถานะ', 1)
    ->เงื่อนไข('ประเภท', $ประเภท)
    ->condition('พนักงาน', $employee)
    ->condition('วันที่', $date);

  ถ้า (!is_null($entity->id()))
    $query->condition('id', $entity->id(), '<>');

  $workIds = $query->execute();
  ส่งคืนค่าว่าง ($workIds);
}

ฉันยินดีที่จะพบข้อบกพร่องใด ๆ จนถึงตอนนี้รหัสนี้ใช้ได้ดีในกรณีอื่นๆ ทั้งหมด

อัปเดต Drupal::lock()

ฉันใช้สมาชิกกิจกรรม 2 รายเพื่อเพิ่มและเผยแพร่ \Drupal::ล็อค() ตามที่กล่าวไว้ในความคิดเห็น การใช้ xdebug ฉันสามารถยืนยันได้ว่ามีการเรียกใช้โค้ด อย่างไรก็ตาม การล็อกดูเหมือนจะไม่มีผลใดๆ เอกสารสำหรับ ล็อค() ค่อนข้างจำกัด ไม่แน่ใจว่ามีอะไรผิดปกติที่นี่

<?php

เนมสเปซ Drupal\custom_entities\EventSubscriber;

ใช้ Symfony\Component\EventDispatcher\EventSubscriberInterface;
ใช้ Symfony\Component\HttpKernel\Event\RequestEvent;
ใช้ Symfony\Component\HttpKernel\KernelEvents

คลาส JsonApiRequestDBlock ใช้ EventSubscriberInterface {

  /**
   * เพิ่มการล็อกสำหรับคำขอ JSON:API
   *
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   * เหตุการณ์ที่ต้องดำเนินการ
   */
  ฟังก์ชั่นสาธารณะ onRequest (RequestEvent $event) {
    $request = $event->getRequest();
    ถ้า ($request->getRequestFormat() !== 'api_json') {
      กลับ;
    }

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

  }

  /**
   * {@inheritdoc}
   */
  ฟังก์ชั่นคงที่สาธารณะ getSubscribedEvents () {
    $events[KernelEvents::REQUEST][] = ['onRequest'];
    ส่งคืนเหตุการณ์ $;
  }

}

และปลดล็อคหลังจากการตอบสนอง

<?php

เนมสเปซ Drupal\custom_entities\EventSubscriber;

ใช้ Symfony\Component\EventDispatcher\EventSubscriberInterface;
ใช้ Symfony\Component\HttpKernel\Event\ResponseEvent;
ใช้ Symfony\Component\HttpKernel\KernelEvents

คลาส JsonApiResponseDBRelease ใช้ EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  ฟังก์ชั่นคงที่สาธารณะ getSubscribedEvents () {
    $events[KernelEvents::RESPONSE][] = ['onResponse'];
    ส่งคืนเหตุการณ์ $;
  }


  /**
   * รีลีสการตอบสนอง JSON:API
   *
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
   * เหตุการณ์ที่ต้องดำเนินการ
   */
  ฟังก์ชั่นสาธารณะ onResponse (ResponseEvent $event) {
    $response = $event->getResponse();
    ถ้า (strpos($response->headers->get('Content-Type'), 'application/vnd.api+json') === FALSE) {
      กลับ;
    }
    $request = $event->getRequest();
    ถ้า ($request->attributes->get('_route') === 'jsonapi.custom_type--work.collection.post' &&
      $request->attributes->get('_controller') === 'jsonapi.entity_resource:createIndividual'
    ) {
      // ปลดล็อค
      $lock = \Drupal::lock();
      ถ้า (!$lock->lockMayBeAvailable('custom_create_lock'))
        $lock->release('custom_create_lock');
    }
  }

}

นี้ถูกเพิ่มเข้าไปใน บริการ.yml

  # สมาชิกกิจกรรม
  custom_entities.jsonapi_db_lock.subscriber:
    คลาส: Drupal\custom_entities\EventSubscriber\JsonApiRequestDBlock
    แท็ก:
      - { ชื่อ: event_subscriber }
  custom_entities.jsonapi_response_db_release.subscriber:
    คลาส: Drupal\custom_entities\EventSubscriber\JsonApiResponseDBRelease
    แท็ก:
      - { ชื่อ: event_subscriber }
Jaypan avatar
de flag
ดูเหมือนว่ามันไม่ควรเกิดขึ้น เพราะแทบจะเป็นไปไม่ได้เลยที่จะช่วยชีวิตสองสิ่งในเวลาเดียวกัน หนึ่งสิ่งต้องเกิดขึ้นก่อนอีกสิ่งหนึ่ง ดังนั้นสิ่งที่สองควรถูกจำกัดโดยข้อจำกัด คุณแน่ใจหรือไม่ว่ารหัสข้อจำกัดของคุณถูกต้อง
cn flag
ฉันเห็นด้วยและฉันค่อนข้างสับสนในขณะนี้ ฉันเพิ่มคำสั่ง `\Drupal::logger` ภายใน `isUnique()` เพื่อบันทึกเวลาปัจจุบัน มันถูกเรียกหลายครั้งในวินาทีเดียวกัน
apaderno avatar
us flag
บรรทัด `$this->entityTypeManager->getStorage('custom_type')->getQuery()` ขาดการเรียกไปยัง `accessCheck(FALSE)` ซึ่งจำเป็นสำหรับการสืบค้นเพื่อเพิกเฉยต่อสิทธิ์การเข้าถึงใดๆ ของผู้ใช้ที่เข้าสู่ระบบ อาจมีสำหรับเอนทิตีที่สอบถาม
4uk4 avatar
cn flag
ฉันไม่คิดว่านี่เป็นปัญหาที่นี่ มันเกี่ยวกับคำขอที่เกิดขึ้นพร้อมกัน ดังที่ @Jaypan ตั้งข้อสังเกตว่าไม่น่าเป็นไปได้มากที่จะส่งข้อมูลฟิลด์เดียวกันสองครั้งภายในหนึ่งหรือสองวินาที แต่ถ้าสิ่งนี้สามารถเกิดขึ้นได้ คุณต้องมีกลไกการล็อคบางอย่าง อาจเป็นไปไม่ได้ที่จะล็อกฐานข้อมูลในตารางที่เกี่ยวข้อง ดังนั้นคุณต้องมีกลไกการล็อกอิสระ เช่น https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Lock%21LockBackendInterface.php/ กลุ่ม/ล็อค
cn flag
@4k4 ฉันเห็นด้วย ไม่น่าเป็นไปได้อย่างยิ่งที่จะมีการส่งข้อมูลฟิลด์เดียวกันสองครั้งและไม่เคยเกิดขึ้นกับฉันมาก่อน อย่างไรก็ตาม มันสามารถเกิดขึ้นได้ ฉันคิดเกี่ยวกับการล็อกฐานข้อมูลด้วย และขอบคุณความคิดเห็นของคุณ ฉันจะตรวจสอบลิงก์กลไกการล็อก
4uk4 avatar
cn flag
คุณต้องใช้การล็อกกับคำขอทั้งหมด ไม่ใช่เฉพาะข้อจำกัด โดยการแทนที่ตัวควบคุมหรือโดยการใช้สมาชิกเหตุการณ์ KernelEvents::REQUEST เพื่อขอรับการล็อก KernelEvents::RESPONSE เพื่อปลดล็อกและ KernelEvents::EXCEPTION เพื่อจัดการข้อยกเว้นที่เกิดขึ้นเนื่องจากการล็อก
cn flag
@ 4k4 ฉันได้อัปเดตคำถามที่แสดงถึงความพยายามในการทำงานกับการล็อก ดูเหมือนจะไม่มีผล ฉันอาจจะยังคิดถึง smth
4uk4 avatar
cn flag
การล็อคของคุณไม่ได้ทำอะไรเลย หากคุณไม่ได้รับล็อคคุณจะต้องทำการยกเว้น คุณสามารถเลือกที่จะวนลูปรอก่อนที่จะดำเนินการนี้
Score:2
ธง us

โดยไม่เห็นโค้ดทั้งหมดที่ใช้สำหรับ custom_type เอนทิตีรวมถึงรหัสสำหรับตัวจัดการและตอบคำถามว่าทำไมรหัสไม่พบรายการที่ซ้ำกัน มีสอง "ข้อบกพร่อง" ที่ฉันสามารถจินตนาการได้ว่าเป็นไปได้ในรหัสที่แสดง

"ข้อบกพร่อง" ประการแรกคือข้อความค้นหาสำหรับเอนทิตีที่มีอยู่นั้นมีข้อจำกัดมากกว่าที่ควร ซึ่งหมายความว่าจะตรวจสอบฟิลด์เอนทิตีที่ไม่เกี่ยวข้องกับเอนทิตีที่จะทำซ้ำ ตัวอย่างเช่น:

  • เดอะ สถานะ ฟิลด์ สมมติว่าเป็น สถานะ ฟิลด์ที่ใช้โดยหน่วยงานหลักของ Drupal
  • เดอะ วันที่ ฟิลด์ สมมติว่ามีวันที่/เวลาประทับที่สร้าง

เอนทิตีหลัก Drupal เดียวที่ใช้การตรวจสอบเอนทิตีเพื่อหลีกเลี่ยงเอนทิตีที่ซ้ำกันถูกสร้างขึ้นคือ เส้นทาง_นามแฝง นิติบุคคลดำเนินการโดย PathAlias ระดับ. นิติบุคคลนั้นมี สถานะ ฟิลด์ รองรับการแก้ไข แต่ไม่มีฟิลด์สำหรับจัดเก็บเมื่อสร้างขึ้น และไม่มีเอนทิตีเจ้าของ (เช่นโหนดทำ)
UniquePathAliasConstraintValidator เป็นตัวตรวจสอบข้อจำกัดของเอนทิตี รหัสสำหรับ UniquePathAliasConstraintValidator::validate() คืออันต่อไปนี้.

  $path = $entity->getPath();
  $alias = $entity->getAlias();
  $langcode = $entity->language()->getId();
  $storage = $this->entityTypeManager->getStorage('path_alias');
  $query = $storage->getQuery()
    -> การเข้าถึงตรวจสอบ (เท็จ)
    ->เงื่อนไข ('นามแฝง', $นามแฝง, '=')
    ->condition('langcode', $langcode, '=');
  ถ้า (!$entity->isNew()) {
    $query->condition('id', $entity->id(), '<>');
  }
  ถ้า ($ เส้นทาง) {
    $query->condition('เส้นทาง', $เส้นทาง, '<>');
  }
  ถ้า ($result = $query->range(0, 1)->execute()) {
    $existing_alias_id = รีเซ็ต (ผลลัพธ์ $);
    $existing_alias = $storage->load($existing_alias_id);
    ถ้า ($existing_alias->getAlias() !== $นามแฝง) {
      $this->context->buildViolation($constraint->differentCapitalizationMessage, ['%alias' => $alias, '%stored_alias' => $existing_alias->getAlias()])
        ->เพิ่มการละเมิด ();
    }
    อื่น {
      $this->context->buildViolation($constraint->message, ['%alias' => $alias])
        ->เพิ่มการละเมิด ();
    }
  }
}

โดยใช้โค้ดนั้นเป็นตัวอย่าง และใช้เฉพาะโค้ดที่ตรวจสอบซ้ำอย่างเคร่งครัด ในกรณีของคุณ ฉันจะใช้โค้ดต่อไปนี้ (ฉันแสดงเฉพาะรหัสสำหรับ isUnique().)

ฟังก์ชันส่วนตัว isUnique (CustomType $entity) {
  $date = $entity->get('date')->value;
  $type = $entity->bundle();
  $employee = $entity->get('employee')->target_id;
  $query = $this->entityTypeManager->getStorage('custom_type')
    ->getQuery()
    -> การเข้าถึงตรวจสอบ (เท็จ)
    ->เงื่อนไข('ประเภท', $ประเภท)
    ->condition('พนักงาน', $employee)
    ->condition('วันที่', $date);

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

  $result = $query->range(0, 1)->execute();
  ส่งคืนค่าว่าง (ผลลัพธ์ $);
}

ฉันเพิ่มการโทรไปที่ การเข้าถึงการตรวจสอบ (เท็จ) เพราะหากไม่เห็นรหัสที่ใช้โดยตัวจัดการการเข้าถึงเอนทิตี และไม่ทราบว่าเอนทิตีมีเจ้าของหรือไม่ ฉันไม่สามารถแยกการใช้งาน isUnique() ไม่พบรายการที่ซ้ำกันเนื่องจากผู้ใช้ที่เข้าสู่ระบบในปัจจุบันไม่มีสิทธิ์เข้าถึงรายการที่ซ้ำกัน (หรือเอนทิตีใดๆ ของประเภทนั้น) โดยไม่ต้องโทร การเข้าถึงการตรวจสอบ (เท็จ)แบบสอบถามจะส่งคืนเอนทิตีที่ผู้ใช้ที่เข้าสู่ระบบในปัจจุบันสามารถเข้าถึงได้
(สายที่ขาดไป การเข้าถึงการตรวจสอบ (เท็จ) เป็นอีกอันหนึ่งที่เป็นไปได้ "ข้อบกพร่อง" ที่ฉันเห็นในรหัสที่แสดง)

cn flag
ขอบคุณ! ฉันได้ลองใช้รหัสนี้ เล่นและทดสอบหลายครั้ง อย่างไรก็ตาม ปัญหาจริงยังคงอยู่ ด้วยคำขอพร้อมกัน 2 รายการ เครื่องมือตรวจสอบความถูกต้องของฉันไม่ได้ป้องกันเอนทิตีที่ซ้ำกัน ฉันกำลังขุดลึกลงไปและอัปเดตปัญหานี้ให้ทันสมัยอยู่เสมอ
apaderno avatar
us flag
โค้ดของคุณใช้ฟิลด์ *date* เหมือนกับฟิลด์ *created* ที่ใช้สำหรับโหนดหรือไม่ หากเป็นกรณีนี้ ฉันจะไม่ค้นหาเอนทิตีที่มีอยู่ซึ่งมีค่าเหมือนกันสำหรับ *วันที่*
cn flag
ไม่ค่อยแน่ใจว่าคุณหมายถึงอะไร แต่วันที่มีความสำคัญอย่างยิ่งสำหรับการตรวจสอบเอกลักษณ์
apaderno avatar
us flag
ฉันหมายความว่าหากฟิลด์นั้นมีตอนที่สร้างเอนทิตี ฉันจะไม่รวมฟิลด์นั้นในแบบสอบถามเพื่อค้นหารายการที่ซ้ำกัน เห็นได้ชัดว่า หากเอนทิตีมีไว้สำหรับกิจกรรม เช่น และ *วันที่* เป็นวันที่กำหนดกิจกรรม ฉันจะรวมไว้ด้วย

โพสต์คำตอบ

คนส่วนใหญ่ไม่เข้าใจว่าการถามคำถามมากมายจะปลดล็อกการเรียนรู้และปรับปรุงความสัมพันธ์ระหว่างบุคคล ตัวอย่างเช่น ในการศึกษาของ Alison แม้ว่าผู้คนจะจำได้อย่างแม่นยำว่ามีคำถามกี่ข้อที่ถูกถามในการสนทนา แต่พวกเขาไม่เข้าใจความเชื่อมโยงระหว่างคำถามและความชอบ จากการศึกษาทั้ง 4 เรื่องที่ผู้เข้าร่วมมีส่วนร่วมในการสนทนาด้วยตนเองหรืออ่านบันทึกการสนทนาของผู้อื่น ผู้คนมักไม่ตระหนักว่าการถามคำถามจะมีอิทธิพลหรือมีอิทธิพลต่อระดับมิตรภาพระหว่างผู้สนทนา