Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.57% covered (success)
98.57%
69 / 70
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntitySchemaPatcher
98.57% covered (success)
98.57%
69 / 70
80.00% covered (warning)
80.00%
4 / 5
22
0.00% covered (danger)
0.00%
0 / 1
 patchSchema
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 patchFingerprint
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 patchTermlist
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 patchTerm
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 patchString
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\Services\Diff;
6
7use Diff\DiffOp\AtomicDiffOp;
8use Diff\DiffOp\Diff\Diff;
9use Diff\DiffOp\DiffOp;
10use Diff\DiffOp\DiffOpAdd;
11use Diff\DiffOp\DiffOpChange;
12use Diff\DiffOp\DiffOpRemove;
13use Diff\Patcher\PatcherException;
14use EntitySchema\Services\Converter\FullArrayEntitySchemaData;
15
16/**
17 * @license GPL-2.0-or-later
18 */
19class EntitySchemaPatcher {
20
21    /**
22     * @param FullArrayEntitySchemaData $baseSchema
23     * @param Diff $patch
24     *
25     * @return FullArrayEntitySchemaData
26     *
27     * @throws PatcherException throws exception if patch cannot be applied
28     */
29    public function patchSchema( FullArrayEntitySchemaData $baseSchema, Diff $patch ): FullArrayEntitySchemaData {
30        $patchedSchema = $this->patchFingerprint( $baseSchema->data, $patch );
31
32        $patchedSchema['schemaText'] = $this->patchString(
33            $baseSchema->data['schemaText'] ?? '',
34            $patch['schemaText'] ?? null
35        );
36
37        return new FullArrayEntitySchemaData( $patchedSchema );
38    }
39
40    private function patchFingerprint( array $baseSchema, Diff $patch ): array {
41        $aliasGroupPatcher = new AliasGroupListPatcher();
42
43        $patchedSchema = [
44            'labels' => $this->patchTermlist(
45                $baseSchema['labels'] ?? [],
46                $patch['labels'] ?? null
47            ),
48            'descriptions' => $this->patchTermlist(
49                $baseSchema['descriptions'] ?? [],
50                $patch['descriptions'] ?? null
51            ),
52            'aliases' => $aliasGroupPatcher->patchAliasGroupList(
53                $baseSchema['aliases'] ?? [],
54                $patch['aliases'] ?? null
55            ),
56        ];
57
58        return $patchedSchema;
59    }
60
61    private function patchTermlist( array $terms, Diff $patch = null ): array {
62        if ( $patch === null ) {
63            return $terms;
64        }
65        foreach ( $patch as $lang => $diffOp ) {
66            $terms = $this->patchTerm( $terms, $lang, $diffOp );
67        }
68        return $terms;
69    }
70
71    private function patchTerm( array $terms, string $lang, AtomicDiffOp $diffOp ): array {
72        switch ( true ) {
73            case $diffOp instanceof DiffOpAdd:
74                if ( !empty( $terms[$lang] ) ) {
75                    throw new PatcherException( 'Term already exists' );
76                }
77                $terms[$lang] = $diffOp->getNewValue();
78                break;
79
80            case $diffOp instanceof DiffOpChange:
81                if ( empty( $terms[$lang] )
82                    || $terms[$lang] !== $diffOp->getOldValue()
83                ) {
84                    throw new PatcherException( 'Term had been changed' );
85                }
86                $terms[$lang] = $diffOp->getNewValue();
87                break;
88
89            case $diffOp instanceof DiffOpRemove:
90                if ( !empty( $terms[$lang] )
91                    && $terms[$lang] !== $diffOp->getOldValue()
92                ) {
93                    throw new PatcherException( 'Term had been changed' );
94                }
95                unset( $terms[$lang] );
96                break;
97
98            default:
99                throw new PatcherException( 'Invalid terms diff' );
100        }
101
102        return $terms;
103    }
104
105    /**
106     * @param string $base
107     * @param DiffOp|null $diffOp
108     *
109     * @return string
110     */
111    private function patchString( string $base, DiffOp $diffOp = null ): string {
112        switch ( true ) {
113            case $diffOp instanceof DiffOpAdd:
114                $from = '';
115                $to = $diffOp->getNewValue();
116                break;
117            case $diffOp instanceof DiffOpRemove:
118                $from = $diffOp->getOldValue();
119                $to = '';
120                break;
121            case $diffOp instanceof DiffOpChange:
122                $from = $diffOp->getOldValue();
123                $to = $diffOp->getNewValue();
124                break;
125            default:
126                $from = $to = null;
127        }
128        if ( $from !== $to ) {
129            $ok = wfMerge(
130                $from,
131                $to,
132                $base,
133                $result
134            );
135            if ( !$ok ) {
136                throw new PatcherException( 'Patching the Schema failed because it has been changed.' );
137            }
138            return trim( $result );
139        }
140
141        return $base;
142    }
143
144}