Completed
Push — master ( 852bc6...6d1705 )
by Jeroen De
02:21
created

PagePropsIdGenerator::makePropertyName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace IdGenerator\PackagePrivate;
6
7
use IdGenerator\IdGenerator;
8
use RuntimeException;
9
use Wikimedia\Rdbms\IDatabase;
10
use Wikimedia\Rdbms\ILoadBalancer;
11
12
class PagePropsIdGenerator implements IdGenerator {
13
14
	private const FAKE_PAGE_ID = -42;
15
16
	private $loadBalancer;
17
18 1
	public function __construct( ILoadBalancer $loadBalancer ) {
19 1
		$this->loadBalancer = $loadBalancer;
20 1
	}
21
22 1
	public function getNewId( string $type = '' ): int {
23 1
		$database = $this->loadBalancer->getConnection( DB_MASTER );
24
25 1
		$id = $this->generateNewId( $database, $type );
26
27 1
		$this->loadBalancer->reuseConnection( $database );
28
29 1
		return $id;
30
	}
31
32
	/**
33
	 * Generates and returns a new ID.
34
	 *
35
	 * @param IDatabase $database
36
	 * @param string $type
37
	 * @param bool $retry Retry once in case of e.g. race conditions. Defaults to true.
38
	 *
39
	 * @throws RuntimeException
40
	 * @return int
41
	 */
42 1
	private function generateNewId( IDatabase $database, string $type, $retry = true ): int {
43 1
		$database->startAtomic( __METHOD__ );
44
45 1
		$currentId = $database->selectRow(
46 1
			'page_props',
47 1
			'pp_value',
48 1
			$this->getWhere( $type ),
49 1
			__METHOD__,
50 1
			[ 'FOR UPDATE' ]
51
		);
52
53 1
		if ( is_object( $currentId ) ) {
54
			$id = $currentId->id_value + 1;
55
			$success = $database->update(
56
				'page_props',
57
				[ 'pp_value' => $id ],
58
				$this->getWhere( $type )
59
			);
60
		} else {
61 1
			$id = 1;
62
63 1
			$success = $database->insert(
64 1
				'page_props',
65 1
				$this->getInsertValues( $type, $id )
66
			);
67
68
			// Retry once, since a race condition on initial insert can cause one to fail.
69
			// Race condition is possible due to occurrence of phantom reads is possible
70
			// at non serializable transaction isolation level.
71 1
			if ( !$success && $retry ) {
72
				$id = $this->generateNewId( $database, $type, false );
73
				$success = true;
74
			}
75
		}
76
77 1
		$database->endAtomic( __METHOD__ );
78
79 1
		if ( !$success ) {
80
			throw new RuntimeException( 'Could not generate a reliably unique ID.' );
81
		}
82
83 1
		return $id;
84
	}
85
86 1
	private function getInsertValues( string $idType, int $id ): array {
87 1
		$values = $this->getWhere( $idType );
88 1
		$values['pp_value'] = $id;
89 1
		return $values;
90
	}
91
92 1
	private function getWhere( string $idType ): array {
93
		return [
94 1
			'pp_page' => self::FAKE_PAGE_ID,
95 1
			'pp_propname' => $this->makePropertyName( $idType )
96
		];
97
	}
98
99 1
	private function makePropertyName( string $idType ): string {
100 1
		return 'id_' . $idType;
101
	}
102
103
}
104