Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractRdbmsCrud
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 13
650
0.00% covered (danger)
0.00%
0 / 1
 getClassColumnsPrefix
n/a
0 / 0
n/a
0 / 0
0
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getIdentityColumn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTable
n/a
0 / 0
n/a
0 / 0
0
 getColumns
n/a
0 / 0
n/a
0 / 0
0
 getAllColumns
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 load
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 update
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 delete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getByConditions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 loadByConditions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 listByConditions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 deserializeRow
n/a
0 / 0
n/a
0 / 0
0
 deserializeRowIdentity
n/a
0 / 0
n/a
0 / 0
0
 serializeFields
n/a
0 / 0
n/a
0 / 0
0
 deserializeString
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 deserializeInt
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 deserializeUuid
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 deserializeTimestamp
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\WikispeechSpeechDataCollector\Crud\Rdbms;
4
5/**
6 * @file
7 * @ingroup Extensions
8 * @license GPL-2.0-or-later
9 */
10
11use MediaWiki\WikispeechSpeechDataCollector\Crud\AbstractCrud;
12use MediaWiki\WikispeechSpeechDataCollector\Crud\CrudContext;
13use MediaWiki\WikispeechSpeechDataCollector\Domain\Persistent;
14use MediaWiki\WikispeechSpeechDataCollector\Uuid;
15use MWTimestamp;
16
17/**
18 * Database mapping and access for
19 * instances of the corresponding underlying subclass of {@link Persistent}.
20 *
21 * @since 0.1.0
22 */
23abstract class AbstractRdbmsCrud extends AbstractCrud {
24
25    /** @var string Prefix of all extension tables in database. */
26    protected const TABLES_PREFIX = 'wikispeech_sdc_';
27
28    /** @var string Prefix of all columns in database extension tables. */
29    protected const COLUMNS_PREFIX = 'wssdc';
30
31    /**
32     * @return string COLUMNS_PREFIX . 'class prefix' . '_'
33     */
34    abstract protected function getClassColumnsPrefix(): string;
35
36    /** @var string Name of table column that holds the identity. */
37    private $identityColumn;
38
39    /**
40     * @param CrudContext $context
41     * @since 0.1.0
42     */
43    public function __construct( CrudContext $context ) {
44        parent::__construct( $context );
45        $this->identityColumn = $this->getClassColumnsPrefix() . 'identity';
46    }
47
48    /**
49     * Single column identity for this CRUD.
50     *
51     * If you want to use a multi column identity you'll probably have to extract
52     * this method and all uses of it to a strategy pattern or something.
53     *
54     * @return string
55     * @since 0.1.0
56     */
57    public function getIdentityColumn(): string {
58        return $this->identityColumn;
59    }
60
61    /**
62     * @return string Name of database table representing this class.
63     * @since 0.1.0
64     */
65    abstract protected function getTable(): string;
66
67    /**
68     * @return string[] Columns in table required to deserialize an instance, identity excluded.
69     * @since 0.1.0
70     */
71    abstract protected function getColumns(): array;
72
73    /**
74     * @var string[] Columns in table required to deserialize an instance, identity included.
75     * @since 0.1.0
76     */
77    private $allColumns;
78
79    /**
80     * @return array|string[] Columns in table required to deserialize an instance, identity included
81     * @since 0.1.0
82     */
83    protected function getAllColumns(): array {
84        if ( !$this->allColumns ) {
85            $allColumns = $this->getColumns();
86            array_push( $allColumns, $this->getIdentityColumn() );
87            $this->allColumns = $allColumns;
88        }
89        return $this->allColumns;
90    }
91
92    /**
93     * Given a persistent domain object instance with at least identity set,
94     * updates the domain object to correspond data retrieved from the database.
95     *
96     * @see read()
97     * @param Persistent $instance Instance to be loaded. Identity must be set.
98     * @return bool true if found, false if not found.
99     * @since 0.1.0
100     */
101    public function load(
102        Persistent $instance
103    ): bool {
104        return $this->loadByConditions( [
105            $this->getIdentityColumn() => $instance->getIdentity()
106        ], $instance );
107    }
108
109    /**
110     * Given a persistent domain object instance with at least identity set,
111     * updates the database to correspond to the data set in the domain object.
112     *
113     * @param Persistent $instance
114     * @since 0.1.0
115     */
116    public function update(
117        Persistent $instance
118    ): void {
119        $set = $this->serializeFields( $instance );
120        $dbw = $this->getContext()->getDbLoadBalancer()->getConnection( DB_PRIMARY );
121        $dbw->update( $this->getTable(), $set, [
122            $this->getIdentityColumn() => $instance->getIdentity()
123        ], __METHOD__ );
124    }
125
126    /**
127     * Given an identity,
128     * removes the corresponding persistent domain object from the database.
129     *
130     * @param mixed $identity
131     * @since 0.1.0
132     */
133    public function delete(
134        $identity
135    ): void {
136        $dbw = $this->getContext()->getDbLoadBalancer()->getConnection( DB_PRIMARY );
137        $dbw->delete( $this->getTable(), [
138            $this->getIdentityColumn() => $identity
139        ], __METHOD__ );
140    }
141
142    /**
143     * Picks up a single instance. Creates a new object instance.
144     *
145     * @param array $conditions See {@link \Wikimedia\Rdbms\IDatabase::select} conditions.
146     * @return Persistent|null
147     * @since 0.1.0
148     */
149    public function getByConditions(
150        array $conditions
151    ): ?Persistent {
152        $instance = $this->instanceFactory();
153        return $this->loadByConditions( $conditions, $instance ) ? $instance : null;
154    }
155
156    /**
157     * Picks up a single instance. Loads to existing object instance.
158     *
159     * @param array $conditions See {@link \Wikimedia\Rdbms\IDatabase::select} conditions.
160     * @param Persistent $instance Instance to be populated with data.
161     * @return bool true if found, false if not found.
162     * @since 0.1.0
163     */
164    public function loadByConditions(
165        array $conditions,
166        Persistent $instance
167    ): bool {
168        $dbr = $this->getContext()->getDbLoadBalancer()->getConnection( DB_REPLICA );
169        $row = $dbr->selectRow( $this->getTable(), $this->getAllColumns(), $conditions, __METHOD__ );
170        if ( !$row ) {
171            return false;
172        }
173        $rowArray = (array)$row;
174        $this->deserializeRowIdentity( $instance, $rowArray );
175        $this->deserializeRow( $instance, $rowArray );
176        return true;
177    }
178
179    /**
180     * @param array $conditions
181     * @return Persistent[]|null
182     * @since 0.1.0
183     */
184    public function listByConditions(
185        array $conditions
186    ): ?array {
187        $dbr = $this->getContext()->getDbLoadBalancer()->getConnection( DB_REPLICA );
188        $res = $dbr->select( $this->getTable(), $this->getAllColumns(), $conditions, __METHOD__ );
189        $instances = [];
190        foreach ( $res as $row ) {
191            $rowArray = (array)$row;
192            $instance = $this->instanceFactory();
193            $this->deserializeRowIdentity( $instance, $rowArray );
194            $this->deserializeRow( $instance, $rowArray );
195            array_push( $instances, $instance );
196        }
197        return $instances;
198    }
199
200    /**
201     * Reads all class fields except the identity field from the row.
202     *
203     * @param mixed $instance An instance of the corresponding underlying Persistent subclass.
204     * @param array $row
205     * @since 0.1.0
206     */
207    abstract protected function deserializeRow(
208        $instance,
209        array $row
210    ): void;
211
212    /**
213     * @param Persistent $instance
214     * @param array $row
215     * @since 0.1.0
216     */
217    abstract protected function deserializeRowIdentity(
218        Persistent $instance,
219        $row
220    ): void;
221
222    /**
223     * Adds all class fields except for the identity field
224     * to the array using the table column name as key.
225     *
226     * Used to execute create- and update statements.
227     *
228     * @param mixed $instance An instance of the corresponding underlying Persistent subclass.
229     * @return array
230     * @since 0.1.0
231     */
232    abstract protected function serializeFields(
233        $instance
234    ): array;
235
236    /**
237     * @param array $row
238     * @param string $columnName
239     * @return string|null
240     * @since 0.1.0
241     */
242    protected function deserializeString( array $row, string $columnName ): ?string {
243        if ( !array_key_exists( $columnName, $row ) ) {
244            return null;
245        }
246        return $row[$columnName] !== null ? strval( $row[$columnName] ) : null;
247    }
248
249    /**
250     * @param array $row
251     * @param string $columnName
252     * @return int|null
253     * @since 0.1.0
254     */
255    protected function deserializeInt( array $row, string $columnName ): ?int {
256        if ( !array_key_exists( $columnName, $row ) ) {
257            return null;
258        }
259        return $row[$columnName] !== null ? intval( $row[$columnName] ) : null;
260    }
261
262    /**
263     * @param array $row
264     * @param string $columnName
265     * @return string|null
266     * @since 0.1.0
267     */
268    protected function deserializeUuid( array $row, string $columnName ): ?string {
269        if ( !array_key_exists( $columnName, $row ) ) {
270            return null;
271        }
272        return $row[$columnName] !== null ? Uuid::asBytes( strval( $row[$columnName] ) ) : null;
273    }
274
275    /**
276     * @param array $row
277     * @param string $columnName
278     * @return MWTimestamp|null
279     * @since 0.1.0
280     */
281    protected function deserializeTimestamp( array $row, string $columnName ): ?MWTimestamp {
282        if ( !array_key_exists( $columnName, $row ) ) {
283            return null;
284        }
285        return $row[$columnName] !== null ? new MWTimestamp( $row[$columnName] ) : null;
286    }
287}