Answers.Body: <p>Here's my findings after some experimentation:</p>
  
  <ol>
  <li>Elements and Records are two entirely separate entities. The Element is basically the core Craft database entry, while the Record is the additional database entry in a new table with your custom fields.</li>
  <li>Validation should always be done on the element if you use the pattern used in the question, because <code>saveElement()</code> will call validate on the element itself, not the record. If you validate on the record and it fails (<code>$record-&gt;save()</code> returning false), the phantom element will still be created, but no record will refer to it. Yikes.</li>
  <li>When validating on the element, some rules such as <code>unique</code> require access to the database, which is not bound to the element per se. In that case, you should map the <code>targetClass</code> parameter of the rule to the record class.</li>
  <li>However that does mess up same-model detection with <code>UniqueValidator</code>: the validation will fail even if you're editing the current model, saying its unique field is already in use (by itself!).</li>
  <li>Thankfully, you can use the <code>filter</code> parameter of the rule to manually ignore the current ID. However, you must make sure not to use that is the ID is null (i.e. when creating a new element), because <code>!=</code> does not work with <code>NULL</code> values in SQL (you should be using <code>IS NOT</code> instead).</li>
  </ol>
  
  <p>Summary: here's how to implement unique validation on the Element class:</p>
  
  <pre><code>    $rules[] = 
      [ 
          'myField', 
          'unique',
          'targetClass' =&gt; MyRecord::class,
          'filter' =&gt; function($query) {
              if ($this-&gt;id !== null) {
                  $query-&gt;andWhere('`id` != :id', [ 'id' =&gt; $this-&gt;id ] );
              }
          }
      ];
  </code></pre>
  
  <p>That's it!</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-08-29T15:49:45.937Z
Answers.LastActivityDate: 2018-08-29T17:17:56.180Z
Answers.LastEditDate: 2018-08-29T17:17:56.180Z
Answers.UserDisplayName: Lazlo
Answers.Body: <p>I know, this is an old question, but it might help others in the future.</p>
  <p>You can add the method <code>defineRules()</code> to your element class, here is a sample from category element:</p>
  <pre><code>     * @inheritdoc
       */
      protected function defineRules(): array
      {
          $rules = parent::defineRules();
          $rules[] = [['groupId', 'newParentId'], 'number', 'integerOnly' =&gt; true];
          return $rules;
      }
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-11T16:22:47.197Z
Answers.LastActivityDate: 2020-07-11T16:22:47.197Z
Answers.UserDisplayName: phaetons
Body: <p>I'm trying to validate a custom Element in my Craft 3 plugin.</p>
  
  <p>Where should I be putting my validation rules?</p>
  
  <p>I have two classes: one for the <code>Element</code>, one for the <code>ActiveRecord</code> (e.g. <code>Invoice</code> and <code>InvoiceRecord</code>).</p>
  
  <p>One of the requirements is that one of its field (e.g. <code>number</code>) has to be unique.</p>
  
  <p>If I override <code>rules()</code> on the Element, the validation occurs normally but the <code>unique</code> rule throws an error:</p>
  
  <blockquote>
    <p>Call to undefined method Invoice::getDb()</p>
  </blockquote>
  
  <p>Now if I try to add the <code>unique</code> rule to my Record's <code>rule()</code> override, it does seem to fail validation and prevent saving the record, but no validation error is thrown (Craft flashes the "saved successfully" message and returns to the element index, but without the new/updated element).</p>
  
  <p>I understand that the record's validation is called only in <code>afterSave</code>, so technically validation has already been done:</p>
  
  <pre><code>public function afterSave(bool $isNew)
  {
      if ($isNew) 
      {
          $record = new InvoiceRecord();
          $record-&gt;id = $this-&gt;id;
      } 
      else 
      {
          $record = InvoiceRecord::findOne($this-&gt;id);
      }
  
      $record-&gt;number = $this-&gt;number; // Has to be unique
  
      $record-&gt;save();
  
      parent::afterSave($isNew);
  }
  </code></pre>
  
  <p>But I don't understand how to get my validation working for something rather simple.</p>
  
  <p>I can't find anything about validation in the Craft 3 plugin docs or migration docs from Craft 2, does anybody know?</p>
  
Tags: <plugin-development><validation>
Title: How to validate a unique field in Craft 3 element?
ThreadText: # How to validate a unique field in Craft 3 element?
  
  (Asked by: username_0 on 2018-08-27)
  
  I'm trying to validate a custom Element in my Craft 3 plugin.
  
  Where should I be putting my validation rules?
  
  I have two classes: one for the `Element`, one for the `ActiveRecord` (e.g. `Invoice` and `InvoiceRecord`).
  
  One of the requirements is that one of its field (e.g. `number`) has to be unique.
  
  If I override `rules()` on the Element, the validation occurs normally but the `unique` rule throws an error:
  
  > Call to undefined method Invoice::getDb()
  
  Now if I try to add the `unique` rule to my Record's `rule()` override, it does seem to fail validation and prevent saving the record, but no validation error is thrown (Craft flashes the "saved successfully" message and returns to the element index, but without the new/updated element).
  
  I understand that the record's validation is called only in `afterSave`, so technically validation has already been done:
  
  ```
  public function afterSave(bool $isNew)
  {
      if ($isNew) 
      {
          $record = new InvoiceRecord();
          $record->id = $this->id;
      } 
      else 
      {
          $record = InvoiceRecord::findOne($this->id);
      }
  
      $record->number = $this->number; // Has to be unique
  
      $record->save();
  
      parent::afterSave($isNew);
  }
  
  ```
  
  But I don't understand how to get my validation working for something rather simple.
  
  I can't find anything about validation in the Craft 3 plugin docs or migration docs from Craft 2, does anybody know?
  
  **Tags:** plugin-development, validation
  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_0 on 2018-08-29. Score: 4
  
  Here's my findings after some experimentation:
  
  1. Elements and Records are two entirely separate entities. The Element is basically the core Craft database entry, while the Record is the additional database entry in a new table with your custom fields.
  2. Validation should always be done on the element if you use the pattern used in the question, because `saveElement()` will call validate on the element itself, not the record. If you validate on the record and it fails (`$record->save()` returning false), the phantom element will still be created, but no record will refer to it. Yikes.
  3. When validating on the element, some rules such as `unique` require access to the database, which is not bound to the element per se. In that case, you should map the `targetClass` parameter of the rule to the record class.
  4. However that does mess up same-model detection with `UniqueValidator`: the validation will fail even if you're editing the current model, saying its unique field is already in use (by itself!).
  5. Thankfully, you can use the `filter` parameter of the rule to manually ignore the current ID. However, you must make sure not to use that is the ID is null (i.e. when creating a new element), because `!=` does not work with `NULL` values in SQL (you should be using `IS NOT` instead).
  
  Summary: here's how to implement unique validation on the Element class:
  
  ```
      $rules[] = 
      [ 
          'myField', 
          'unique',
          'targetClass' => MyRecord::class,
          'filter' => function($query) {
              if ($this->id !== null) {
                  $query->andWhere('`id` != :id', [ 'id' => $this->id ] );
              }
          }
      ];
  
  ```
  
  That's it!
  
  
  ---
  
  ### Answer by username_1 on 2020-07-11. Score: 1
  
  I know, this is an old question, but it might help others in the future.
  
  You can add the method `defineRules()` to your element class, here is a sample from category element:
  
  ```
       * @inheritdoc
       */
      protected function defineRules(): array
      {
          $rules = parent::defineRules();
          $rules[] = [['groupId', 'newParentId'], 'number', 'integerOnly' => true];
          return $rules;
      }
  
  ```
  
  
  ---
  
  