Completed
Push — add/changelog-tooling ( fa9ac3...7f5585 )
by
unknown
517:08 queued 507:24
created

ChangeEntry::jsonSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
2
/**
3
 * Class representing a change entry.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
9
10
namespace Automattic\Jetpack\Changelog;
11
12
use DateTime;
13
use InvalidArgumentException;
14
use JsonSerializable;
15
16
/**
17
 * Class representing a change entry.
18
 */
19
class ChangeEntry implements JsonSerializable {
20
21
	/**
22
	 * Entry significance.
23
	 *
24
	 * @var string|null
25
	 */
26
	protected $significance = null;
27
28
	/**
29
	 * Entry timestamp.
30
	 *
31
	 * @var DateTime
32
	 */
33
	protected $timestamp;
34
35
	/**
36
	 * Subheading the entry is under.
37
	 *
38
	 * @var string
39
	 */
40
	protected $subheading = '';
41
42
	/**
43
	 * Author of the entry.
44
	 *
45
	 * @var string
46
	 */
47
	protected $author = '';
48
49
	/**
50
	 * Content of the entry.
51
	 *
52
	 * @var string
53
	 */
54
	protected $content = '';
55
56
	/**
57
	 * Constructor.
58
	 *
59
	 * @param array $data Data for entry fields. Keys correspond to the setters, e.g. key 'content' calls `setContent()`.
60
	 * @throws InvalidArgumentException If an argument is invalid.
61
	 */
62 View Code Duplication
	public function __construct( array $data = array() ) {
63
		$data = $data + array( 'timestamp' => 'now' );
64
		foreach ( $data as $k => $v ) {
65
			$func = array( $this, 'set' . ucfirst( $k ) );
66
			if ( is_callable( $func ) ) {
67
				$func( $v );
68
			} else {
69
				throw new InvalidArgumentException( __METHOD__ . ": Unrecognized data item \"$k\"." );
70
			}
71
		}
72
	}
73
74
	/**
75
	 * Compare two ChangeEntry objects.
76
	 *
77
	 * @param ChangeEntry $a First ChangeEntry.
78
	 * @param ChangeEntry $b Second ChangeEntry.
79
	 * @param array       $config Comparison configuration. Keys are:
80
	 *        - ordering: (string[]) Order in which to consider the fields. Field
81
	 *          names correspond to getters, e.g. 'significance' =>
82
	 *          `getSignificance()`. Default ordering is subheading, content.
83
	 *        - knownSubheadings: (string[]) Change entries with these headings will
84
	 *          compare, in this order, after those with no subheading and before any
85
	 *          other subheadings.
86
	 * @return int <0 if $a should come before $b, >0 if $b should come before $a, or 0 otherwise.
87
	 * @throws InvalidArgumentException If an argument is invalid.
88
	 */
89
	public static function compare( ChangeEntry $a, ChangeEntry $b, array $config = array() ) {
90
		$config += array(
91
			'ordering'         => array( 'subheading', 'content' ),
92
			'knownSubheadings' => array(),
93
		);
94
95
		foreach ( $config['ordering'] as $field ) {
96
			// First, check for a custom comparison function.
97
			$func = array( static::class, 'compare' . ucfirst( $field ) );
98
			if ( is_callable( $func ) ) {
99
				$ret = $func( $a, $b, $config );
100
			} else {
101
				// Otherwise, just use `strnatcasecmp()`.
102
				$func = 'get' . ucfirst( $field );
103
				if ( ! is_callable( array( $a, $func ) ) || ! is_callable( array( $b, $func ) ) ) {
104
					throw new InvalidArgumentException( __METHOD__ . ': Invalid field in ordering' );
105
				}
106
				$aa  = call_user_func( array( $a, $func ) );
107
				$bb  = call_user_func( array( $b, $func ) );
108
				$ret = strnatcasecmp( $aa, $bb );
109
			}
110
			if ( 0 !== $ret ) {
111
				return $ret;
112
			}
113
		}
114
115
		return 0;
116
	}
117
118
	/**
119
	 * Get the significance.
120
	 *
121
	 * @return string|null
122
	 */
123
	public function getSignificance() {
124
		return $this->significance;
125
	}
126
127
	/**
128
	 * Set the significance.
129
	 *
130
	 * @param string|null $significance 'patch', 'minor', or 'major'.
131
	 * @returns $this
132
	 * @throws InvalidArgumentException If an argument is invalid.
133
	 */
134
	public function setSignificance( $significance ) {
135
		if ( ! in_array( $significance, array( null, 'patch', 'minor', 'major' ), true ) ) {
136
			throw new InvalidArgumentException( __METHOD__ . ": Significance must be 'patch', 'minor', or 'major' (or null)" );
137
		}
138
		$this->significance = $significance;
139
		return $this;
140
	}
141
142
	/**
143
	 * Compare significance values.
144
	 *
145
	 * @param ChangeEntry $a First entry.
146
	 * @param ChangeEntry $b Second entry.
147
	 * @param array       $config Unused.
148
	 * @return int
149
	 */
150
	protected static function compareSignificance( ChangeEntry $a, ChangeEntry $b, array $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
151
		static $values = array( 'major', 'minor', 'patch', null );
152
		$aa            = array_search( $a->getSignificance(), $values, true );
153
		$bb            = array_search( $b->getSignificance(), $values, true );
154
		return $aa - $bb;
155
	}
156
157
	/**
158
	 * Get the timestamp.
159
	 *
160
	 * @return DateTime
161
	 */
162
	public function getTimestamp() {
163
		return $this->timestamp;
164
	}
165
166
	/**
167
	 * Set the timestamp.
168
	 *
169
	 * @param DateTime|string $timestamp Timestamp to set.
170
	 * @returns $this
171
	 * @throws InvalidArgumentException If an argument is invalid.
172
	 */
173 View Code Duplication
	public function setTimestamp( $timestamp ) {
174
		if ( ! $timestamp instanceof DateTime ) {
175
			try {
176
				$timestamp = new DateTime( $timestamp );
177
			} catch ( \Exception $ex ) {
178
				throw new InvalidArgumentException( __METHOD__ . ': Invalid timestamp', 0, $ex );
179
			}
180
		}
181
		$this->timestamp = $timestamp;
182
		return $this;
183
	}
184
185
	/**
186
	 * Compare timestamps.
187
	 *
188
	 * @param ChangeEntry $a First entry.
189
	 * @param ChangeEntry $b Second entry.
190
	 * @param array       $config Unused.
191
	 * @return int
192
	 */
193
	protected static function compareTimestamp( ChangeEntry $a, ChangeEntry $b, array $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
194
		$aa = $a->getTimestamp();
195
		$bb = $b->getTimestamp();
196
		return $aa < $bb ? -1 : ( $aa > $bb ? 1 : 0 );
197
	}
198
199
	/**
200
	 * Get the subheading.
201
	 *
202
	 * @return string
203
	 */
204
	public function getSubheading() {
205
		return $this->subheading;
206
	}
207
208
	/**
209
	 * Set the subheading.
210
	 *
211
	 * @param string $subheading Subheading to set.
212
	 * @returns $this
213
	 */
214
	public function setSubheading( $subheading ) {
215
		$this->subheading = (string) $subheading;
216
		return $this;
217
	}
218
219
	/**
220
	 * Compare subheadings.
221
	 *
222
	 * @param ChangeEntry $a First entry.
223
	 * @param ChangeEntry $b Second entry.
224
	 * @param array       $config Used for 'knownSubheadings'.
225
	 * @return int
226
	 */
227
	protected static function compareSubheading( ChangeEntry $a, ChangeEntry $b, array $config ) {
228
		$aa = $a->getSubheading();
229
		$bb = $b->getSubheading();
230
231
		// If they're equal, just return that.
232
		$cmp = strnatcasecmp( $aa, $bb );
233
		if ( 0 === $cmp ) {
234
			return 0;
235
		}
236
237
		// Empty string comes first.
238
		if ( '' === $aa ) {
239
			return -1;
240
		}
241
		if ( '' === $bb ) {
242
			return 1;
243
		}
244
245
		// Search for known values.
246
		foreach ( $config['knownSubheadings'] as $v ) {
247
			if ( strnatcasecmp( $aa, $v ) === 0 ) {
248
				return -1;
249
			}
250
			if ( strnatcasecmp( $bb, $v ) === 0 ) {
251
				return 1;
252
			}
253
		}
254
255
		// Fallback.
256
		return $cmp;
257
	}
258
259
	/**
260
	 * Get the author.
261
	 *
262
	 * @return string
263
	 */
264
	public function getAuthor() {
265
		return $this->author;
266
	}
267
268
	/**
269
	 * Set the author.
270
	 *
271
	 * @param string $author Author to set.
272
	 * @returns $this
273
	 */
274
	public function setAuthor( $author ) {
275
		$this->author = (string) $author;
276
		return $this;
277
	}
278
279
	/**
280
	 * Get the content.
281
	 *
282
	 * @return string
283
	 */
284
	public function getContent() {
285
		return $this->content;
286
	}
287
288
	/**
289
	 * Set the content.
290
	 *
291
	 * @param string $content Content to set.
292
	 * @returns $this
293
	 */
294
	public function setContent( $content ) {
295
		$this->content = (string) $content;
296
		return $this;
297
	}
298
299
	/**
300
	 * Return data for serializing to JSON.
301
	 *
302
	 * @return array
303
	 */
304
	public function jsonSerialize() {
305
		return array(
306
			'__class__'    => static::class,
307
			'significance' => $this->significance,
308
			'timestamp'    => $this->timestamp->format( 'c' ),
309
			'subheading'   => $this->subheading,
310
			'author'       => $this->author,
311
			'content'      => $this->content,
312
		);
313
	}
314
315
	/**
316
	 * Unserialize from JSON.
317
	 *
318
	 * @param array $data JSON data as returned by self::jsonSerialize().
319
	 * @return static
320
	 * @throws InvalidArgumentException If the data is invalid.
321
	 */
322
	public static function jsonUnserialize( $data ) {
323
		$data = (array) $data;
324
		if ( ! isset( $data['__class__'] ) ) {
325
			throw new InvalidArgumentException( 'Invalid data' );
326
		}
327
		$class = $data['__class__'];
328
		unset( $data['__class__'] );
329 View Code Duplication
		if ( ! class_exists( $class ) || ! is_a( $class, static::class, true ) ) {
330
			throw new InvalidArgumentException( "Cannot instantiate $class via " . static::class . '::' . __FUNCTION__ );
331
		}
332
		return new $class( $data );
333
	}
334
335
}
336