Test Failed
Pull Request — main (#64)
by Lode
03:16
created

Validator::clearUsedFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace alsvanzelf\jsonapi\helpers;
4
5
use alsvanzelf\jsonapi\exceptions\DuplicateException;
6
use alsvanzelf\jsonapi\exceptions\InputException;
7
use alsvanzelf\jsonapi\interfaces\ResourceInterface;
8
9
/**
10
 * @internal
11
 */
12
class Validator {
13
	const OBJECT_CONTAINER_TYPE          = 'type';
14
	const OBJECT_CONTAINER_ID            = 'id';
15
	const OBJECT_CONTAINER_ATTRIBUTES    = 'attributes';
16
	const OBJECT_CONTAINER_RELATIONSHIPS = 'relationships';
17
	
18
	/** @var array */
19
	protected $usedFields = [];
20
	/** @var array */
21
	protected $usedResourceIdentifiers = [];
22
	/** @var array */
23
	protected static $defaults = [
24
		/**
25
		 * blocks 'type' as a keyword inside attributes or relationships
26
		 * the specification doesn't allow this as 'type' is already set at the root of a resource
27
		 * set to true if migrating to jsonapi and currently using 'type' as attribute or relationship
28
		 */
29
		'enforceTypeFieldNamespace' => true,
30
	];
31
	
32
	/**
33
	 * block if already existing in another object, otherwise just overwrite
34
	 * 
35
	 * @see https://jsonapi.org/format/1.1/#document-resource-object-fields
36
	 * 
37
	 * @param  string[] $fieldName
38
	 * @param  string   $objectContainer one of the Validator::OBJECT_CONTAINER_* constants
39
	 * @param  array    $options         optional {@see Validator::$defaults}
40
	 * 
41
	 * @throws DuplicateException
42
	 */
43
	public function claimUsedFields(array $fieldNames, $objectContainer, array $options=[]) {
44
		$options = array_merge(self::$defaults, $options);
45
		
46
		foreach ($fieldNames as $fieldName) {
47
			if (isset($this->usedFields[$fieldName]) === false) {
48
				$this->usedFields[$fieldName] = $objectContainer;
49
				continue;
50
			}
51
			if ($this->usedFields[$fieldName] === $objectContainer) {
52
				continue;
53
			}
54
			
55
			/**
56
			 * @note this is not allowed by the specification
57
			 */
58
			if ($this->usedFields[$fieldName] === Validator::OBJECT_CONTAINER_TYPE && $options['enforceTypeFieldNamespace'] === false) {
59
				continue;
60
			}
61
			
62
			throw new DuplicateException('field name "'.$fieldName.'" already in use at "data.'.$this->usedFields[$fieldName].'"');
63
		}
64
	}
65
	
66
	/**
67
	 * @param string $objectContainer one of the Validator::OBJECT_CONTAINER_* constants
68
	 */
69
	public function clearUsedFields($objectContainerToClear) {
70
		foreach ($this->usedFields as $fieldName => $containerFound) {
71
			if ($containerFound !== $objectContainerToClear) {
72
				continue;
73
			}
74
			
75
			unset($this->usedFields[$fieldName]);
76
		}
77
	}
78
	
79
	/**
80
	 * @param  ResourceInterface $resource
81
	 * 
82
	 * @throws InputException if no type or id has been set on the resource
83
	 * @throws DuplicateException if the combination of type and id has been set before
84
	 */
85
	public function claimUsedResourceIdentifier(ResourceInterface $resource) {
86
		if ($resource->getResource()->hasIdentification() === false) {
87
			throw new InputException('can not validate resource without identifier, set type and id first');
88
		}
89
		
90
		$resourceKey = $resource->getResource()->getIdentificationKey();
91
		if (isset($this->usedResourceIdentifiers[$resourceKey]) === false) {
92
			$this->usedResourceIdentifiers[$resourceKey] = true;
93
			return;
94
		}
95
		
96
		throw new DuplicateException('can not have multiple resources with the same identification');
97
	}
98
	
99
	/**
100
	 * @see https://jsonapi.org/format/1.1/#document-member-names
101
	 * 
102
	 * @todo allow non-url safe chars
103
	 * 
104
	 * @param  string $memberName
105
	 * 
106
	 * @throws InputException
107
	 */
108
	public static function checkMemberName($memberName) {
109
		$globallyAllowedCharacters  = 'a-zA-Z0-9';
110
		$generallyAllowedCharacters = $globallyAllowedCharacters.'_-';
111
		
112
		$regex = '{^
113
			(
114
				['.$globallyAllowedCharacters.']
115
				
116
				|
117
				
118
				['.$globallyAllowedCharacters.']
119
				['.$generallyAllowedCharacters.']*
120
				['.$globallyAllowedCharacters.']
121
			)
122
		$}x';
123
		
124
		if (preg_match($regex, $memberName) === 1) {
125
			return;
126
		}
127
		
128
		throw new InputException('invalid member name "'.$memberName.'"');
129
	}
130
	
131
	/**
132
	 * @param  string|int $httpStatusCode
133
	 * @return boolean
134
	 */
135
	public static function checkHttpStatusCode($httpStatusCode) {
136
		$httpStatusCode = (int) $httpStatusCode;
137
		
138
		if ($httpStatusCode < 100) {
139
			return false;
140
		}
141
		if ($httpStatusCode >= 600) {
142
			return false;
143
		}
144
		
145
		return true;
146
	}
147
}
148