DEV Community

Esteban
Esteban

Posted on

Validating data in PHP objects using magic methods

I've been working with PHP lately and when deserializing a model I wanted to have a generic way of adding validations for its properties, so I wondered whether there was a way for hooking into an object's properties while getting/setting them, much like the Proxy object in javascript.

I found that magic methods in PHP would do the trick. Magic methods overwrite the object's default functionality when the event that triggers them happens. The most commonly used is the __construct() method, the constructor for the object.

Using the __get and __set magic methods, we can hook into the property when getting & setting it and run our custom logic. Hence, creating a bunch of validators to verify we're not assigning them any forbidden values:

class User {
   public $allowedProperties = [ 'firstName', 'email' ];    
   private $firstName;
   private $email;

   public function __set($propertyName, $value) {
      if (!in_array($propertyName, $this->allowedProperties)) {
         throw new Exception('Property: ' . $propertyName . ' not supported');
      }

      $validator = $propertyName . 'Validator';
        $isPropertyValueValid = $this->$validator($value);
        if (!$isPropertyValueValid) {
            throw new Exception('Value: ' . $value . ' for: ' . $propertyName);
        }
        $this->$propertyName = $value;
    }

    public function __get($propertyName) {
        if (!in_array($propertyName, $this->allowedProperties)) {
            throw new Exception('Property: ' . $propertyName . ' not supported');
        }
        return $this->$propertyName;
    }

    private function firstNameValidator($value) {
        return is_string($value) && strlen($value) < 25;
    }

    private function emailValidator($value) {
        return filter_var($value, FILTER_VALIDATE_EMAIL);
    }
}


$user = new User();
$user->firstName = 'Ahhhh I am longer than 25 characters nooooooo'; // throw Exception
$user->email = 'esteban@gmail.com';
Enter fullscreen mode Exit fullscreen mode

It is important to point out that by using this strategy we'll lose any static analysis we have, so it's a choice to make.

Have fun!

Top comments (7)

Collapse
 
suckup_de profile image
Lars Moelleken

What do you think of value objects like First Name or Email that will validate itself, so that you do not need any magic?

e.g.: github.com/voku/value_objects/blob...

Collapse
 
fr0gs profile image
Esteban

They are nice, but they force you to introduce an additional library.

Collapse
 
suckup_de profile image
Lars Moelleken

No, they are just plain (immutable) php objects. 😊

Collapse
 
klabauterjan profile image
JanWennrich

For the given example (and probably in general), using getters and setters is a cleaner approach

Collapse
 
fr0gs profile image
Esteban

Can you provide an example ?

Collapse
 
klabauterjan profile image
JanWennrich • Edited

Sure, here is my example:

<?php

class User {
   private string $firstName;
   private string $email;

   public function setFirstName(string $firstName): void
   {
        if (strlen($firstName) > 25) {
            throw new Exception('Firstname is longer than 25 characters');
        }

        $this->firstName = $firstName;
   }

   public function setEmail(string $email): void
   {
        $this->email = filter_var($email, FILTER_VALIDATE_EMAIL);
   }

   public function getFirstName(): string
   {
        return $this->firstName;
   }

   public function getEmail(): string
   {
        return $this->email;
   }
}


$user = new User();
$user->setFirstName('Ahhhh I am longer than 25 characters nooooooo'); // throws Exception
$user->setEmail('esteban@gmail.com');
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
fr0gs profile image
Esteban

Good point :P