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

PagePropsIdGenerator   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 92
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 80.95%

Importance

Changes 0
Metric Value
wmc 10
lcom 1
cbo 0
dl 0
loc 92
ccs 34
cts 42
cp 0.8095
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getNewId() 0 9 1
B generateNewId() 0 43 5
A getInsertValues() 0 5 1
A getWhere() 0 6 1
A makePropertyName() 0 3 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