Validator   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Test Coverage

Coverage 97.37%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 135
ccs 37
cts 38
cp 0.9737
rs 10
wmc 17

5 Methods

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