Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
78.72% |
74 / 94 |
|
30.00% |
3 / 10 |
CRAP | |
0.00% |
0 / 1 |
| LexemePatcher | |
78.72% |
74 / 94 |
|
30.00% |
3 / 10 |
51.91 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| canPatchEntityType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| patchEntity | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
| getPatchedItemId | |
60.00% |
6 / 10 |
|
0.00% |
0 / 1 |
6.60 | |||
| patchNextFormId | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
4.03 | |||
| patchNextSenseId | |
12.50% |
1 / 8 |
|
0.00% |
0 / 1 |
14.72 | |||
| patchForms | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
7.02 | |||
| patchAddForm | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
| patchSenses | |
64.29% |
9 / 14 |
|
0.00% |
0 / 1 |
9.23 | |||
| patchAddSense | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Wikibase\Lexeme\Domain\Diff; |
| 4 | |
| 5 | use Diff\DiffOp\Diff\Diff; |
| 6 | use Diff\DiffOp\DiffOpAdd; |
| 7 | use Diff\DiffOp\DiffOpChange; |
| 8 | use Diff\DiffOp\DiffOpRemove; |
| 9 | use Diff\Patcher\PatcherException; |
| 10 | use InvalidArgumentException; |
| 11 | use Wikibase\DataModel\Entity\EntityDocument; |
| 12 | use Wikibase\DataModel\Entity\ItemId; |
| 13 | use Wikibase\DataModel\Services\Diff\EntityDiff; |
| 14 | use Wikibase\DataModel\Services\Diff\EntityPatcherStrategy; |
| 15 | use Wikibase\DataModel\Services\Diff\StatementListPatcher; |
| 16 | use Wikibase\DataModel\Services\Diff\TermListPatcher; |
| 17 | use Wikibase\Lexeme\Domain\Model\Exceptions\ConflictException; |
| 18 | use Wikibase\Lexeme\Domain\Model\Lexeme; |
| 19 | use Wikibase\Lexeme\Domain\Model\LexemePatchAccess; |
| 20 | use Wikimedia\Assert\Assert; |
| 21 | |
| 22 | /** |
| 23 | * @license GPL-2.0-or-later |
| 24 | * @author Amir Sarabadani <ladsgroup@gmail.com> |
| 25 | * @author Thiemo Kreuz |
| 26 | */ |
| 27 | class LexemePatcher implements EntityPatcherStrategy { |
| 28 | |
| 29 | /** |
| 30 | * @var TermListPatcher |
| 31 | */ |
| 32 | private $termListPatcher; |
| 33 | |
| 34 | /** |
| 35 | * @var StatementListPatcher |
| 36 | */ |
| 37 | private $statementListPatcher; |
| 38 | |
| 39 | /** |
| 40 | * @var FormPatcher |
| 41 | */ |
| 42 | private $formPatcher; |
| 43 | |
| 44 | /** |
| 45 | * @var SensePatcher |
| 46 | */ |
| 47 | private $sensePatcher; |
| 48 | |
| 49 | public function __construct() { |
| 50 | $this->termListPatcher = new TermListPatcher(); |
| 51 | $this->statementListPatcher = new StatementListPatcher(); |
| 52 | $this->formPatcher = new FormPatcher(); |
| 53 | $this->sensePatcher = new SensePatcher(); |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * @param string $entityType |
| 58 | * |
| 59 | * @return bool |
| 60 | */ |
| 61 | public function canPatchEntityType( $entityType ) { |
| 62 | return $entityType === Lexeme::ENTITY_TYPE; |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * @param Lexeme $lexeme |
| 67 | * @param LexemeDiff $patch |
| 68 | * |
| 69 | * @throws InvalidArgumentException |
| 70 | */ |
| 71 | public function patchEntity( EntityDocument $lexeme, EntityDiff $patch ) { |
| 72 | Assert::parameterType( Lexeme::class, $lexeme, '$lexeme' ); |
| 73 | Assert::parameterType( LexemeDiff::class, $patch, '$patch' ); |
| 74 | |
| 75 | $this->termListPatcher->patchTermList( $lexeme->getLemmas(), $patch->getLemmasDiff() ); |
| 76 | |
| 77 | $this->statementListPatcher->patchStatementList( |
| 78 | $lexeme->getStatements(), |
| 79 | $patch->getClaimsDiff() |
| 80 | ); |
| 81 | |
| 82 | $itemId = $this->getPatchedItemId( $patch->getLexicalCategoryDiff() ); |
| 83 | if ( $itemId !== false ) { |
| 84 | $lexeme->setLexicalCategory( $itemId ); |
| 85 | } |
| 86 | |
| 87 | $itemId = $this->getPatchedItemId( $patch->getLanguageDiff() ); |
| 88 | if ( $itemId !== false ) { |
| 89 | $lexeme->setLanguage( $itemId ); |
| 90 | } |
| 91 | |
| 92 | $this->patchNextFormId( $lexeme, $patch ); |
| 93 | $this->patchForms( $lexeme, $patch ); |
| 94 | |
| 95 | $this->patchNextSenseId( $lexeme, $patch ); |
| 96 | $this->patchSenses( $lexeme, $patch ); |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * @param Diff $patch |
| 101 | * |
| 102 | * @throws PatcherException |
| 103 | * @return ItemId|null|false False in case the diff is valid, but does not contain a change. |
| 104 | */ |
| 105 | private function getPatchedItemId( Diff $patch ) { |
| 106 | if ( $patch->isEmpty() ) { |
| 107 | return false; |
| 108 | } |
| 109 | |
| 110 | $diffOp = $patch['id']; |
| 111 | |
| 112 | switch ( true ) { |
| 113 | case $diffOp instanceof DiffOpAdd: |
| 114 | return $diffOp->getNewValue(); |
| 115 | |
| 116 | case $diffOp instanceof DiffOpChange: |
| 117 | return $diffOp->getNewValue(); |
| 118 | |
| 119 | case $diffOp instanceof DiffOpRemove: |
| 120 | return null; |
| 121 | } |
| 122 | |
| 123 | throw new PatcherException( 'Invalid ItemId diff' ); |
| 124 | } |
| 125 | |
| 126 | private function patchNextFormId( Lexeme $entity, LexemeDiff $patch ) { |
| 127 | // FIXME: Why is this a loop? The nextFormId field is not an array! |
| 128 | foreach ( $patch->getNextFormIdDiff() as $nextFormIdDiff ) { |
| 129 | if ( !( $nextFormIdDiff instanceof DiffOpChange ) ) { |
| 130 | throw new PatcherException( 'Invalid forms list diff' ); |
| 131 | } |
| 132 | |
| 133 | $newNumber = $nextFormIdDiff->getNewValue(); |
| 134 | if ( $newNumber > $entity->getNextFormId() ) { |
| 135 | $entity->patch( static function ( LexemePatchAccess $patchAccess ) use ( $newNumber ) { |
| 136 | $patchAccess->increaseNextFormIdTo( $newNumber ); |
| 137 | } ); |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | private function patchNextSenseId( Lexeme $entity, LexemeDiff $patch ) { |
| 143 | // FIXME: Same as above |
| 144 | foreach ( $patch->getNextSenseIdDiff() as $nextSenseIdDiff ) { |
| 145 | if ( !( $nextSenseIdDiff instanceof DiffOpChange ) ) { |
| 146 | throw new PatcherException( 'Invalid senses list diff' ); |
| 147 | } |
| 148 | |
| 149 | $newNumber = $nextSenseIdDiff->getNewValue(); |
| 150 | if ( $newNumber > $entity->getNextSenseId() ) { |
| 151 | $entity->patch( static function ( LexemePatchAccess $patchAccess ) use ( $newNumber ) { |
| 152 | $patchAccess->increaseNextSenseIdTo( $newNumber ); |
| 153 | } ); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | private function patchForms( Lexeme $lexeme, LexemeDiff $patch ) { |
| 159 | foreach ( $patch->getFormsDiff() as $formDiff ) { |
| 160 | switch ( true ) { |
| 161 | case $formDiff instanceof AddFormDiff: |
| 162 | $this->patchAddForm( $lexeme, $patch, $formDiff ); |
| 163 | break; |
| 164 | |
| 165 | case $formDiff instanceof RemoveFormDiff: |
| 166 | $lexeme->removeForm( $formDiff->getRemovedFormId() ); |
| 167 | break; |
| 168 | |
| 169 | case $formDiff instanceof ChangeFormDiffOp: |
| 170 | try { |
| 171 | $form = $lexeme->getForm( $formDiff->getFormId() ); |
| 172 | } catch ( \OutOfRangeException ) { |
| 173 | // form does not exist (anymore? may have been removed), nothing to patch (T326768) |
| 174 | break; |
| 175 | } |
| 176 | $this->formPatcher->patch( $form, $formDiff ); |
| 177 | break; |
| 178 | |
| 179 | default: |
| 180 | throw new PatcherException( 'Invalid forms list diff: ' . get_class( $formDiff ) ); |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | private function patchAddForm( Lexeme $lexeme, LexemeDiff $patch, AddFormDiff $formDiff ): void { |
| 186 | $form = $formDiff->getAddedForm(); |
| 187 | $lexeme->patch( |
| 188 | static function ( LexemePatchAccess $patchAccess ) use ( $form, $patch ) { |
| 189 | try { |
| 190 | $patchAccess->addForm( $form ); |
| 191 | } catch ( ConflictException $e ) { |
| 192 | if ( $patch->getNextFormIdDiff()->isEmpty() ) { |
| 193 | // the patch was restoring, not newly adding, a form; |
| 194 | // if a form by this ID already exists, there’s nothing else to do (T392372) |
| 195 | } else { |
| 196 | throw $e; |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | ); |
| 201 | } |
| 202 | |
| 203 | private function patchSenses( Lexeme $lexeme, LexemeDiff $patch ) { |
| 204 | foreach ( $patch->getSensesDiff() as $senseDiff ) { |
| 205 | switch ( true ) { |
| 206 | case $senseDiff instanceof AddSenseDiff: |
| 207 | $this->patchAddSense( $lexeme, $patch, $senseDiff ); |
| 208 | break; |
| 209 | |
| 210 | case $senseDiff instanceof RemoveSenseDiff: |
| 211 | $lexeme->removeSense( $senseDiff->getRemovedSenseId() ); |
| 212 | break; |
| 213 | |
| 214 | case $senseDiff instanceof ChangeSenseDiffOp: |
| 215 | try { |
| 216 | $sense = $lexeme->getSense( $senseDiff->getSenseId() ); |
| 217 | } catch ( \OutOfRangeException ) { |
| 218 | // sense does not exist (anymore? may have been removed), nothing to patch (T284061) |
| 219 | break; |
| 220 | } |
| 221 | $this->sensePatcher->patchEntity( $sense, $senseDiff ); |
| 222 | break; |
| 223 | |
| 224 | default: |
| 225 | throw new PatcherException( 'Invalid senses list diff: ' . get_class( $senseDiff ) ); |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | public function patchAddSense( Lexeme $lexeme, LexemeDiff $patch, AddSenseDiff $senseDiff ): void { |
| 231 | $sense = $senseDiff->getAddedSense(); |
| 232 | $lexeme->patch( |
| 233 | static function ( LexemePatchAccess $patchAccess ) use ( $sense, $patch ) { |
| 234 | try { |
| 235 | $patchAccess->addSense( $sense ); |
| 236 | } catch ( ConflictException $e ) { |
| 237 | if ( $patch->getNextSenseIdDiff()->isEmpty() ) { |
| 238 | // the patch was restoring, not newly adding, a sense; |
| 239 | // if a sense by this ID already exists, there’s nothing else to do (T392372) |
| 240 | } else { |
| 241 | throw $e; |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | ); |
| 246 | } |
| 247 | |
| 248 | } |