-
-
Notifications
You must be signed in to change notification settings - Fork 504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
doc: Review and test validation cookbook #2662
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ is allowed to: | |
|
||
<?php | ||
|
||
#[Document] | ||
class Order | ||
{ | ||
public function assertCustomerAllowedBuying(): void | ||
|
@@ -68,8 +69,7 @@ First Attributes: | |
#[HasLifecycleCallbacks] | ||
class Order | ||
{ | ||
#[PrePersist] | ||
#[PreUpdate] | ||
#[PreFlush] | ||
public function assertCustomerAllowedBuying(): void {} | ||
} | ||
|
||
|
@@ -78,17 +78,21 @@ First Attributes: | |
<doctrine-mapping> | ||
<document name="Order"> | ||
<lifecycle-callbacks> | ||
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" /> | ||
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" /> | ||
<lifecycle-callback type="preFlush" method="assertCustomerAllowedBuying" /> | ||
</lifecycle-callbacks> | ||
</document> | ||
</doctrine-mapping> | ||
|
||
Now validation is performed whenever you call | ||
``DocumentManager#persist($order)`` or when you call | ||
``DocumentManager#flush()`` and an order is about to be updated. Any | ||
Exception that happens in the lifecycle callbacks will be cached by | ||
the DocumentManager. | ||
Now validation is performed when you call ``DocumentManager#flush()`` and an | ||
order is about to be inserted or updated. Any Exception that happens in the | ||
lifecycle callbacks will stop the flush operation and the exception will be | ||
propagated. | ||
|
||
You might want to use ``PrePersist`` instead of ``PreFlush`` to validate | ||
the document sooner, when you call ``DocumentManager#persist()``. This way you | ||
can catch validation errors earlier in your application flow. Be aware that | ||
if the document is modified after the ``PrePersist`` event, the validation | ||
might not be triggered again and an invalid document can be persisted. | ||
|
||
Of course you can do any type of primitive checks, not null, | ||
email-validation, string size, integer and date ranges in your | ||
|
@@ -102,8 +106,7 @@ validation callbacks. | |
#[HasLifecycleCallbacks] | ||
class Order | ||
{ | ||
#[PrePersist] | ||
#[PreUpdate] | ||
#[PreFlush] | ||
public function validate(): void | ||
{ | ||
if (!($this->plannedShipDate instanceof DateTime)) { | ||
|
@@ -128,11 +131,8 @@ can register multiple methods for validation in "PrePersist" or | |
"PreUpdate" or mix and share them in any combinations between those | ||
two events. | ||
|
||
There is no limit to what you can and can't validate in | ||
"PrePersist" and "PreUpdate" as long as you don't create new document | ||
instances. This was already discussed in the previous blog post on | ||
the Versionable extension, which requires another type of event | ||
called "onFlush". | ||
There is no limit to what you can validate in ``PreFlush``, ``PrePersist`` and | ||
``PreUpdate`` as long as you don't create new document instances. | ||
|
||
Further readings: :doc:`Lifecycle Events <../reference/events>` | ||
|
||
|
@@ -181,44 +181,44 @@ the ``odm:schema:create`` or ``odm:schema:update`` command. | |
#[ODM\Document] | ||
#[ODM\Validation( | ||
validator: self::VALIDATOR, | ||
action: ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN, | ||
level: ClassMetadata::SCHEMA_VALIDATION_LEVEL_MODERATE, | ||
action: ClassMetadata::SCHEMA_VALIDATION_ACTION_ERROR, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing to throw an error. Reading the server logs is totally disconnected from the application code, a developer will prefer having an exception with the context and the stacktrace. |
||
level: ClassMetadata::SCHEMA_VALIDATION_LEVEL_STRICT, | ||
)] | ||
class SchemaValidated | ||
{ | ||
public const VALIDATOR = <<<'EOT' | ||
{ | ||
"$jsonSchema": { | ||
"required": ["name"], | ||
"properties": { | ||
"name": { | ||
"bsonType": "string", | ||
"description": "must be a string and is required" | ||
} | ||
private const VALIDATOR = <<<'EOT' | ||
{ | ||
"$jsonSchema": { | ||
"required": ["name"], | ||
"properties": { | ||
"name": { | ||
"bsonType": "string", | ||
"description": "must be a string and is required" | ||
} | ||
} | ||
}, | ||
"$or": [ | ||
{ "phone": { "$type": "string" } }, | ||
{ "email": { "$regularExpression" : { "pattern": "@mongodb\\.com$", "options": "" } } }, | ||
{ "status": { "$in": [ "Unknown", "Incomplete" ] } } | ||
] | ||
} | ||
}, | ||
"$or": [ | ||
{ "phone": { "$type": "string" } }, | ||
{ "email": { "$regularExpression" : { "pattern": "@mongodb\\.com$", "options": "" } } }, | ||
{ "status": { "$in": [ "Unknown", "Incomplete" ] } } | ||
] | ||
} | ||
EOT; | ||
EOT; | ||
|
||
#[ODM\Id] | ||
private $id; | ||
public string $id; | ||
|
||
#[ODM\Field(type: 'string')] | ||
private $name; | ||
public string $name; | ||
|
||
#[ODM\Field(type: 'string')] | ||
private $phone; | ||
public string $phone; | ||
|
||
#[ODM\Field(type: 'string')] | ||
private $email; | ||
public string $email; | ||
|
||
#[ODM\Field(type: 'string')] | ||
private $status; | ||
public string $status; | ||
} | ||
|
||
.. code-block:: xml | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1229,7 +1229,7 @@ for the related collection. | |
)] | ||
class SchemaValidated | ||
{ | ||
public const VALIDATOR = <<<'EOT' | ||
private const VALIDATOR = <<<'EOT' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to use a public constant when it's read by the attribute on the class, that was required for the doctrine annotation. |
||
{ | ||
"$jsonSchema": { | ||
"required": ["name"], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -325,20 +325,51 @@ | |
public const REFERENCE_STORE_AS_REF = 'ref'; | ||
|
||
/** | ||
* The collection schema validationAction values | ||
* Rejects any insert or update that violates the validation criteria. | ||
* | ||
* @see https://docs.mongodb.com/manual/core/schema-validation/#accept-or-reject-invalid-documents | ||
* Value for collection schema validationAction. | ||
* | ||
* @see https://www.mongodb.com/docs/manual/core/schema-validation/handle-invalid-documents/#option-1--reject-invalid-documents | ||
*/ | ||
public const SCHEMA_VALIDATION_ACTION_ERROR = 'error'; | ||
public const SCHEMA_VALIDATION_ACTION_WARN = 'warn'; | ||
|
||
/** | ||
* The collection schema validationLevel values | ||
* MongoDB allows the operation to proceed, but records the violation in the MongoDB log. | ||
* | ||
* Value for collection schema validationAction. | ||
* | ||
* @see https://www.mongodb.com/docs/manual/core/schema-validation/handle-invalid-documents/#option-2--allow-invalid-documents--but-record-them-in-the-log | ||
*/ | ||
public const SCHEMA_VALIDATION_ACTION_WARN = 'warn'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only updated comments to have a better tooltip in the IDE. The constant definition haven't been modified. |
||
|
||
/** | ||
* Disable schema validation for the collection. | ||
* | ||
* Value of validationLevel. | ||
* | ||
* @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/ | ||
*/ | ||
public const SCHEMA_VALIDATION_LEVEL_OFF = 'off'; | ||
|
||
/** | ||
* MongoDB applies the same validation rules to all document inserts and updates. | ||
* | ||
* Value of validationLevel. | ||
* | ||
* @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/#steps--use-strict-validation | ||
*/ | ||
public const SCHEMA_VALIDATION_LEVEL_STRICT = 'strict'; | ||
|
||
/** | ||
* MongoDB applies the same validation rules to document inserts and updates | ||
* to existing valid documents that match the validation rules. Updates to | ||
* existing documents in the collection that don't match the validation rules | ||
* aren't checked for validity. | ||
* | ||
* Value of validationLevel. | ||
* | ||
* @see https://docs.mongodb.com/manual/core/schema-validation/#existing-documents | ||
* @see https://www.mongodb.com/docs/manual/core/schema-validation/specify-validation-level/#steps--use-moderate-validation | ||
*/ | ||
public const SCHEMA_VALIDATION_LEVEL_OFF = 'off'; | ||
public const SCHEMA_VALIDATION_LEVEL_STRICT = 'strict'; | ||
public const SCHEMA_VALIDATION_LEVEL_MODERATE = 'moderate'; | ||
|
||
/* The inheritance mapping types */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Documentation\Validation; | ||
|
||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id; | ||
|
||
#[Document] | ||
class Customer | ||
{ | ||
#[Id] | ||
public string $id; | ||
|
||
public function __construct( | ||
#[Field(type: 'float')] | ||
public float $orderLimit, | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Documentation\Validation; | ||
|
||
use RuntimeException; | ||
|
||
class CustomerOrderLimitExceededException extends RuntimeException | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Documentation\Validation; | ||
|
||
use Doctrine\Common\Collections\ArrayCollection; | ||
use Doctrine\Common\Collections\Collection; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\EmbedMany; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\HasLifecycleCallbacks; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\PreFlush; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\ReferenceOne; | ||
|
||
#[Document] | ||
#[HasLifecycleCallbacks] | ||
class Order | ||
{ | ||
#[Id] | ||
public string $id; | ||
|
||
public function __construct( | ||
#[ReferenceOne(targetDocument: Customer::class)] | ||
public Customer $customer, | ||
/** @var Collection<OrderLine> */ | ||
#[EmbedMany(targetDocument: OrderLine::class)] | ||
public Collection $orderLines = new ArrayCollection(), | ||
) { | ||
} | ||
|
||
/** @throw CustomerOrderLimitExceededException */ | ||
#[PreFlush] | ||
public function assertCustomerAllowedBuying(): void | ||
{ | ||
$orderLimit = $this->customer->orderLimit; | ||
|
||
$amount = 0; | ||
foreach ($this->orderLines as $line) { | ||
$amount += $line->amount; | ||
} | ||
|
||
if ($amount > $orderLimit) { | ||
throw new CustomerOrderLimitExceededException(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Documentation\Validation; | ||
|
||
use Doctrine\ODM\MongoDB\Mapping\Annotations\EmbeddedDocument; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id; | ||
|
||
#[EmbeddedDocument] | ||
class OrderLine | ||
{ | ||
#[Id] | ||
public string $id; | ||
|
||
public function __construct( | ||
#[Field(type: 'float')] | ||
public float $amount, | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Documentation\Validation; | ||
|
||
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; | ||
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; | ||
|
||
#[ODM\Document] | ||
#[ODM\Validation( | ||
validator: self::VALIDATOR, | ||
action: ClassMetadata::SCHEMA_VALIDATION_ACTION_ERROR, | ||
level: ClassMetadata::SCHEMA_VALIDATION_LEVEL_STRICT, | ||
)] | ||
class SchemaValidated | ||
{ | ||
private const VALIDATOR = <<<'EOT' | ||
{ | ||
"$jsonSchema": { | ||
"required": ["name"], | ||
"properties": { | ||
"name": { | ||
"bsonType": "string", | ||
"description": "must be a string and is required" | ||
} | ||
} | ||
}, | ||
"$or": [ | ||
{ "phone": { "$type": "string" } }, | ||
{ "email": { "$regularExpression" : { "pattern": "@mongodb\\.com$", "options": "" } } }, | ||
{ "status": { "$in": [ "Unknown", "Incomplete" ] } } | ||
] | ||
} | ||
EOT; | ||
|
||
#[ODM\Id] | ||
public string $id; | ||
|
||
#[ODM\Field(type: 'string')] | ||
public string $name; | ||
|
||
#[ODM\Field(type: 'string')] | ||
public string $phone; | ||
|
||
#[ODM\Field(type: 'string')] | ||
public string $email; | ||
|
||
#[ODM\Field(type: 'string')] | ||
public string $status; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this cookbook was copied from a blog post.