Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Rule
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
13
100.00% covered (success)
100.00%
1 / 1
 parse
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
8
 isHasClass
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isHasOneClass
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Validator;
16
17use Pop\Utils\Str;
18
19/**
20 * Validator rule class
21 *
22 * @category   Pop
23 * @package    Pop\Validator
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    5.0.0
28 */
29class Rule
30{
31
32    /**
33     * "Has" classes
34     * @var array
35     */
36    protected static array $hasClasses = [
37        'HasOneThatEquals', 'HasOnlyOneThatEquals', 'HasCountEqual', 'HasCountGreaterThanEqual',
38        'HasCountGreaterThan', 'HasCountLessThanEqual', 'HasCountLessThan', 'HasCountNotEqual',
39        'HasOneThatContains', 'HasOnlyOneThatContains', 'HasOneIn',
40        'HasOneGreaterThan', 'HasOneGreaterThanEqual', 'HasOneLessThan', 'HasOneLessThanEqual',
41        'HasOnlyOneGreaterThan', 'HasOnlyOneGreaterThanEqual', 'HasOnlyOneLessThan', 'HasOnlyOneLessThanEqual',
42        'HasOneDateTimeThatEquals', 'HasOneDateTimeGreaterThan', 'HasOneDateTimeGreaterThanEqual',
43        'HasOneDateTimeLessThan', 'HasOneDateTimeLessThanEqual',
44        'HasOnlyOneDateTimeThatEquals', 'HasOnlyOneDateTimeGreaterThan', 'HasOnlyOneDateTimeGreaterThanEqual',
45        'HasOnlyOneDateTimeLessThan', 'HasOnlyOneDateTimeLessThanEqual',
46    ];
47
48    /**
49     * "Has one" classes
50     * @var array
51     */
52    protected static array $hasOneClasses = [
53        'HasOne', 'HasOnlyOne', 'HasOneNotEmpty',
54    ];
55
56    /**
57     * Parse rule
58     *
59     * @param  string  $rule
60     * @param  ?string $prefix
61     * @throws \InvalidArgumentException
62     * @return array
63     */
64    public static function parse(string $rule, ?string $prefix = 'Pop\Validator\\'): array
65    {
66        $ruleSet = array_map('trim', explode(':', $rule));
67
68        if (count($ruleSet) < 2) {
69            throw new \InvalidArgumentException(
70                'Error: The rule is invalid. It must have at least a field and a validator, e.g. username:not_empty.'
71            );
72        }
73
74        $field     = $ruleSet[0];
75        $validator = Str::snakeCaseToTitleCase($ruleSet[1]);
76        $value     = (!empty($ruleSet[2])) ? $ruleSet[2] : null;
77        $message   = (!empty($ruleSet[3])) ? $ruleSet[3] : null;
78
79        if (!class_exists($prefix . $validator)) {
80            throw new \InvalidArgumentException("Error: The validator class '" . $prefix . $validator . "' does not exist.");
81        }
82
83        if (str_contains($rule, ',')) {
84            $value = array_filter(array_map('trim', explode(',', $value)));
85        }
86
87        if (self::isHasClass($validator, false, $prefix)) {
88            $value = [$field => $value];
89        } else if (self::isHasOneClass($validator, $prefix)) {
90            $value = $field;
91        }
92
93        return [
94            'field'     => $field,
95            'validator' => $validator,
96            'value'     => $value,
97            'message'   => $message,
98        ];
99    }
100
101    /**
102     * Is a "has" class
103     *
104     * @param  string  $class
105     * @param  bool    $both
106     * @param  ?string $prefix
107     * @return bool
108     */
109    public static function isHasClass(string $class, bool $both = false, ?string $prefix = 'Pop\Validator\\'): bool
110    {
111        $prefix ??= 'Pop\Validator\\';
112        if (str_starts_with($class, $prefix)) {
113            $class = substr($class, strlen($prefix));
114        }
115
116        $hasClasses = ($both) ? array_merge(self::$hasClasses, self::$hasOneClasses) : self::$hasClasses;
117
118        return in_array($class, $hasClasses);
119    }
120
121    /**
122     * Is a "has" class
123     *
124     * @param  string  $class
125     * @param  ?string $prefix
126     * @return bool
127     */
128    public static function isHasOneClass(string $class, ?string $prefix = 'Pop\Validator\\'): bool
129    {
130        $prefix ??= 'Pop\Validator\\';
131        if (str_starts_with($class, $prefix)) {
132            $class = substr($class, strlen($prefix));
133        }
134
135        return in_array($class, self::$hasOneClasses);
136    }
137
138}