Data Validation

Introduction

Hint

PostgreSQL Documentation: Data

types are a way to limit the kind

of data that can be stored in a table. For many applications, however, the constraint they provide is too coarse. For example, a column containing a product price should probably only accept positive values. But there is no standard data type that accepts only positive numbers. Another issue is that you might want to constrain column data with respect to other columns or rows. For example, in a table containing product information, there should be only one row for each product number.

Doctrine allows you to define portable constraints on columns and tables. Constraints give you as much control over the data in your tables as you wish. If a user attempts to store data in a column that would violate a constraint, an error is raised. This applies even if the value came from the default value definition.

Doctrine constraints act as database level constraints as well as application level validators. This means double security: the database doesn’t allow wrong kind of values and neither does the application.

Here is a full list of available validators within Doctrine:

Below is an example of how you use the validator and how to specify the arguments for the validators on a column.

In our example we will use the minlength validator.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'minlength' => 12
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      minlength: 12
# ...

Examples

Not Null

A not-null constraint simply specifies that a column must not assume the null value. A not-null constraint is always written as a column constraint.

The following definition uses a notnull constraint for column name. This means that the specified column doesn’t accept null values.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'notnull' => true,
                'primary' => true,
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      notnull: true
      primary: true
# ...

When this class gets exported to database the following SQL statement would get executed (in MySQL):

CREATE TABLE user (username VARCHAR(255) NOT NULL,
PRIMARY KEY(username))

The notnull constraint also acts as an application level validator. This means that if Doctrine validators are turned on, Doctrine will automatically check that specified columns do not contain null values when saved.

If those columns happen to contain null values :php:class:`Doctrine_Validator_Exception` is raised.

Email

The e-mail validator simply validates that the inputted value is indeed a valid e-mail address and that the MX records for the address domain resolve as a valid e-mail address.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('email', 'string', 255, array(
                'email'   => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    email:
      type: string(255)
      email: true
# ...

Now when we try and create a user with an invalid email address it will not validate:

// test.php
// ...
$user           = new User();
$user->username = 'jwage';
$user->email    = 'jonwage';

if ( ! $user->isValid() )
{
    echo 'User is invalid!';
}

The above code will throw an exception because jonwage is not a valid e-mail address. Now we can take this even further and give a valid e-mail address format but an invalid domain name:

// test.php
// ...
$user           = new User();
$user->username = 'jwage';
$user->email    = 'jonwage@somefakedomainiknowdoesntexist.com';

if ( ! $user->isValid() )
{
    echo 'User is invalid!';
}

Now the above code will still fail because the domain somefakedomainiknowdoesntexist.com does not exist and the php function checkdnsrr() returned false.

Not Blank

The not blank validator is similar to the not null validator except that it will fail on empty strings or strings with white space.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'notblank'   => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      notblank: true
# ...

Now if we try and save a User record with a username that is a single blank white space, validation will fail:

// test.php

// ...
$user           = new User();
$user->username = ' ';

if ( ! $user->isValid() )
{
    echo 'User is invalid!';
}

No Space

The no space validator is simple. It checks that the value doesn’t contain any spaces.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'nospace'   => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      nospace: true
# ...

Now if we try and save a User with a username that has a space in it, the validation will fail:

$user           = new User();
$user->username = 'jon wage';

if ( ! $user->isValid() )
{
    echo 'User is invalid!';
}

Past

The past validator checks if the given value is a valid date in the past. In this example we’ll have a User model with a birthday column and we want to validate that the date is in the past.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('birthday', 'timestamp', null, array(
                'past' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    birthday:
      type: timestamp
      past: true
# ...

Now if we try and set a birthday that is not in the past we will get a validation error.

Future

The future validator is the opposite of the past validator and checks if the given value is a valid date in the future. In this example we’ll have a User model with a next_appointment_date column and we want to validate that the date is in the future.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('next_appointment_date', 'timestamp', null, array(
                'future' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    next_appointment_date:
      type: timestamp
      future: true
# ...

Now if we try and set an appointment date that is not in the future we will get a validation error.

Min Length

The min length does exactly what it says. It checks that the value string length is greater than the specified minimum length. In this example we will have a User model with a password column where we want to make sure the length of the password is at least 5 characters long.

// models/User.php
class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('password', 'timestamp', null, array(
                'minlength' => 5
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    password:
      type: timestamp
      minlength: 5
# ...

Now if we try and save a User with a password that is shorter than 5 characters, the validation will fail.

// test.php

// ...
$user           = new User();
$user->username = 'jwage';
$user->password = 'test';

if ( ! $user->isValid() )
{
    echo 'User is invalid because "test" is only 4 characters long!';
}

Country

The country validator checks if the given value is a valid country code.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('country', 'string', 2, array(
                'country' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    country:
      type: string(2)
      country: true
# ...

Now if you try and save a User with an invalid country code the validation will fail.

// test.php

// ...
$user               = new User();
$user->username     = 'jwage';
$user->country_code = 'zz';

if ( ! $user->isValid() )
{
    echo 'User is invalid because "zz" is not a valid country code!';
}

IP Address

The ip address validator checks if the given value is a valid ip address.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('ip_address', 'string', 15, array(
                'ip' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    ip_address:
      type: string(15)
      ip: true
# ...

Now if you try and save a User with an invalid ip address the validation will fail.

$user             = new User();
$user->username   = 'jwage';
$user->ip_address = '123.123';

if ( ! $user->isValid() )
{
    echo 'User is invalid because "123.123" is not a valid ip address';
}

HTML Color

The html color validator checks that the given value is a valid html hex color.

// models/User.php
class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('favorite_color', 'string', 7, array(
                'htmlcolor' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

Now if you try and save a User with an invalid html color value for the favorite_color column the validation will fail.

// test.php

// ...
$user                 = new User();
$user->username       = 'jwage';
$user->favorite_color = 'red';

if ( ! $user->isValid() )
{
    echo 'User is invalid because "red" is not a valid hex color';
}

Range

The range validator checks if value is within given range of numbers.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('age', 'integer', 3, array(
                'range' => array(10, 100)
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    age:
      type: integer(3)
      range: [10, 100]
# ...

Now if you try and save a User with an age that is less than 10 or greater than 100, the validation will fail.

// test.php

// ...
$user           = new User();
$user->username = 'jwage';
$user->age      = '3';

if ( ! $user->isValid() )
{
    echo 'User is invalid because "3" is less than the minimum of "10"';
}

You can use the range validator to validate max and min values by omitting either one of the 0 or 1 keys of the range array. Below is an example:

// models/User.php
class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('age', 'integer', 3, array(
                'range' => array(1 => 100)
            )
        );
    }
}

The above would make it so that age has a max of 100. To have a minimum value simple specify 0 instead of 1 in the range array.

The YAML syntax for this would look like the following:

---
# schema.yml

# ...
User:
  columns:
# ...
    age:
      type: integer(3)
      range:
        1: 100
# ...

Unique

Unique constraints ensure that the data contained in a column or a group of columns is unique with respect to all the rows in the table.

In general, a unique constraint is violated when there are two or more rows in the table where the values of all of the columns included in the constraint are equal. However, two null values are not considered equal in this comparison. That means even in the presence of a unique constraint it is possible to store duplicate rows that contain a null value in at least one of the constrained columns. This behavior conforms to the SQL standard, but some databases do not follow this rule. So be careful when developing applications that are intended to be portable.

The following definition uses a unique constraint for column username.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'unique' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      unique: true
# ...

Note

You should only use unique constraints for columns other than the primary key because they are always unique already.

Regular Expression

The regular expression validator is a simple way to validate column values against your own provided regular expression. In this example we will make sure the username contains only valid letters or numbers.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('username', 'string', 255, array(
                'regexp' => '/[a-zA-Z0-9]/'
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      regexp: '/^[a-zA-Z0-9]+$/'
# ...

Now if we were to try and save a User with a username that has any other character than a letter or number in it, the validation will fail:

// test.php

// ...
$user           = new User();
$user->username = '[jwage';

if ( ! $user->isValid() )
{
    echo 'User is invalid because the username contains a [ character';
}

Credit Card

The credit card validator simply checks that the given value is indeed a valid credit card number.

// models/User.php
class User extends BaseUser
{
    // ...

    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('cc_number', 'integer', 16, array(
                'creditcard' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    cc_number:
      type: integer(16)
      creditcard: true
# ...

Read Only

The read only validator will fail validation if you modify a column that has the readonly validator enabled on it.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('readonly_value', 'string', 255, array(
                'readonly' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    readonly_value:
      type: integer(16)
      readonly: true
# ...

Now if I ever try and modify the column named readonly_value from a User object instance, validation will fail.

Unsigned

The unsigned validator checks that the given integer value is unsigned.

// models/User.php
class User extends BaseUser
{
    // ...
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...
        $this->hasColumn('age', 'integer', 3, array(
                'unsigned' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
  columns:
# ...
    age:
      type: integer(3)
      unsigned: true
# ...

Now if I try and save a User with a negative age the validation will fail:

// test.php

// ...
$user           = new User();
$user->username = 'jwage';
$user->age      = '-100';

if ( ! $user->isValid() )
{
    echo 'User is invalid because -100 is signed';
}

US State

The us state validator checks that the given string is a valid US state code.

// models/State.php
class State extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string', 255);
        $this->hasColumn('code', 'string', 2, array(
                'usstate' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

State:
  columns:
    name: string(255)
    code:
      type: string(2)
      usstate: true

Now if I try and save a State with an invalid state code then the validation will fail.

$state       = new State();
$state->name = 'Tennessee';
$state->code = 'ZZ';

if ( ! $state->isValid() )
{
    echo 'State is invalid because "ZZ" is not a valid state code';
}

Conclusion

If we want Doctrine to validate our data before being persisted to the database, now we have the knowledge on how to do it. We can use the validators that are provided with the Doctrine core to perform common validations of our data.

The Inheritance is an important one as we will discuss a great feature of Doctrine, Inheritance! Inheritance is a great way accomplish complex functionality with minimal code. After we discuss inheritance we will move on to a custom strategy that provides even better functionality than inheritance, called Behaviors.

Fork me on GitHub