Rozbudowa walidatora – tym razem o regułę unique, która wykorzystuje bazę danych. Do dzieła!

Na czym stanęliśmy:

/*
$form_rules =  [
    'email' => ['required', 'email'],
    'password' => ['required'],
    'confirmPassword' => ['required', 'match:password'],
    'name' => ['unique:people.name']
];
*/

$form_data = [ 
    'email' => 'john.doe@gmail.com',
    'password' => md5('helloworld'),
    'confirmPassword' => md5('helloworld'),
    'name' => 'Jane'
];



interface RuleInterface
{
  public function validate(array $data, string $field, array $params): bool;

  public function getMessage(array $data, string $field, array $params): string;
}

class RequiredRule implements RuleInterface
{
  public function validate(array $data, string $field, array $params): bool
  {
    return !empty($data[$field]);
  }

  public function getMessage(array $data, string $field, array $params): string
  {
    return "Field {$field} is missing!";
  }
}



class EmailRule implements RuleInterface
{
  public function validate(array $data, string $field, array $params): bool
  {
    return (bool) filter_var($data[$field], FILTER_VALIDATE_EMAIL);
  }

  public function getMessage(array $data, string $field, array $params): string
  {
    return "Invalid email";
  }
}
class UrlRule implements RuleInterface
{
  public function validate(array $data, string $field, array $params): bool
  {
    return (bool) filter_var($data[$field], FILTER_VALIDATE_URL);
  }

  public function getMessage(array $data, string $field, array $params): string
  {
    return "Invalid URL";
  }
}

class MatchRule implements RuleInterface
{
  public function validate(array $data, string $field, array $params): bool
  {
    $fieldOne = $data[$field];
    $fieldTwo = $data[$params[0]];

    return $fieldOne === $fieldTwo;
  }

  public function getMessage(array $data, string $field, array $params): string
  {
    return "Does not match {$params[0]} field";
  }
}

W tym samym folderze tworzymy plik config.ini:

; This is a sample configuration file
; Comments start with ';', as in php.ini

[DB_CREDENTIALS]
driver = mysql
host = localhost
db_name = 
db_user = 
db_password =
port = 3306

Dopisujemy już nam znaną klasę config:

class ConfigINI {

    public static $iniPath = __DIR__ . "/config.ini";

    public static function getPDOCredentials(){
        $ini_array = parse_ini_file(static::$iniPath, true);
        return $ini_array['DB_CREDENTIALS'];
    }
}

Podobnie znaną już nam klasę PDOConnection:

class PDOConnection {

    protected $connection;
    protected $credentials;

    public static $requiredKeys = [
        'driver',
        'host',
        'db_name',
        'db_user',
        'db_password',
        'port'
    ];

    public function connect(): PDOConnection{
        try {
            $this->credentials = ConfigINI::getPDOCredentials();

            if(!array_key_exists('port', $this->credentials))
                $this->credentials['port'] = 3306;

            if(!static::hasRequiredKeys($this->credentials))
                throw new Error("Credentials dont have valid keys");

            $parsed = static::parseCredentials($this->credentials);
            $this->connection = new PDO(...$parsed);

        } catch (Exception $e) {
            echo $e->getMessage();
        }
        
        return $this;
    }

    public function getConnection(): PDO|null
    {
        return $this->connection;
    }

    public static function parseCredentials($credentials){
        $driver = $credentials['driver'];
        $config = http_build_query(arg_separator:';', data: 
            [
                'host' => $credentials['host'],
                'port' => $credentials['port'],
                'dbname' => $credentials['db_name']
            ]
            );

        $dsn = "{$driver}:{$config}";
       
        return [$dsn, $credentials['db_user'], $credentials['db_password']];
    }

    public static function hasRequiredKeys($credentials){
        foreach(self::$requiredKeys as $key){
            if(!array_key_exists($key, $credentials))
                return false;
        }
        return true;
    }

}

Pora na regułę:

class UniqueRule implements RuleInterface
{
   protected $conn; 

   public function __construct()
   {
    $conn = new PDOConnection();
    $conn->connect();
    $this->conn = $conn->getConnection();
   }

 //(...)
}

Mamy połączenie to teraz validate:

class UniqueRule implements RuleInterface
{
   protected $conn; 
  
  //(...)

  public function validate(array $data, string $field, array $params): bool
  {
  
    $field = $data[$field];
    [$table, $column] = explode(".", $params[0]);
    $q = $this->conn->query("SELECT COUNT(*) FROM {$table} WHERE {$column} = '$field'");
    $cnt =  $q->fetch()[0];
    return $cnt === 0;
  }

  
}

Musimy jeszcze zaimplementować getMessage:

class UniqueRule implements RuleInterface
{
   protected $conn; 
  
  //(...)

  public function getMessage(array $data, string $field, array $params): string
  {
    return "Field {$params[0]} already in use!";
  }
}

Sprawdzamy, jak działa:

<?php 

/*
$form_rules =  [
    'email' => ['required', 'email'],
    'password' => ['required'],
    'confirmPassword' => ['required', 'match:password'],
    'name' => ['unique:people.name']
];
*/

$form_data = [ 
    'email' => 'john.doe@gmail.com',
    'password' => md5('helloworld'),
    'confirmPassword' => md5('helloworld'),
    'name' => 'Jane'
];


//(...)

$field = 'name';
$rule = new UniqueRule;
$params = ['people.name'];

$unique = $rule->validate($form_data, $field, $params);
if(!$unique){
    echo $rule->getMessage($form_data, $field, $params);
}
//Field people.name already in use!

U mnie 'Jane’ jest w bazie danych, ale 'Janet’ już nie ma. Sprawdzamy do woli, czy działa.

Rozbudowa – w następnych lekcjach.