Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.38% covered (warning)
77.38%
65 / 84
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
TtmServerFactory
77.38% covered (warning)
77.38%
65 / 84
28.57% covered (danger)
28.57%
2 / 7
52.84
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getNames
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
7.23
 has
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 create
66.67% covered (warning)
66.67%
12 / 18
0.00% covered (danger)
0.00%
0 / 1
12.00
 getDefaultForQuerying
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 getWritable
74.07% covered (warning)
74.07%
20 / 27
0.00% covered (danger)
0.00%
0 / 1
13.11
 getWriteOnly
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\TtmServer;
5
6use FakeTTMServer;
7use InvalidArgumentException;
8use RemoteTTMServer;
9use TTMServer;
10
11/**
12 * @since 2021.01
13 * @license GPL-2.0-or-later
14 * @author Niklas Laxström
15 */
16class TtmServerFactory {
17    private array $configs;
18    private ?string $default;
19    private const TTMSERVER_CLASSES = [
20        ReadableTtmServer::class,
21        WritableTtmServer::class,
22        SearchableTtmServer::class
23    ];
24
25    /** @see https://www.mediawiki.org/wiki/Help:Extension:Translate/Translation_memories#Configuration */
26    public function __construct( array $configs, ?string $default = null ) {
27        $this->configs = $configs;
28        $this->default = $default;
29    }
30
31    /** @return string[] */
32    public function getNames(): array {
33        $ttmServersIds = [];
34        foreach ( $this->configs as $serviceId => $config ) {
35            $type = $config['type'] ?? '';
36            if ( $type === 'ttmserver' || $type === 'remote-ttmserver' ) {
37                $ttmServersIds[] = $serviceId;
38            }
39
40            // Translation memory configuration may not define a type, in such
41            // cases we determine whether the service is a TTM server using the
42            // interfaces it implements.
43            $serviceClass = $config['class'] ?? null;
44            if ( $serviceClass !== null ) {
45                foreach ( self::TTMSERVER_CLASSES as $ttmClass ) {
46                    if ( $serviceClass instanceof $ttmClass ) {
47                        $ttmServersIds[] = $serviceId;
48                        break;
49                    }
50                }
51            }
52        }
53        return $ttmServersIds;
54    }
55
56    public function has( string $name ): bool {
57        $ttmServersIds = $this->getNames();
58        return in_array( $name, $ttmServersIds );
59    }
60
61    public function create( string $name ): TTMServer {
62        if ( !$this->has( $name ) ) {
63            throw new ServiceCreationFailure( "No configuration for name '$name'" );
64        }
65
66        $config = $this->configs[$name];
67        if ( !is_array( $config ) ) {
68            throw new ServiceCreationFailure( "Invalid configuration for name '$name'" );
69        }
70
71        if ( isset( $config['class'] ) ) {
72            $class = $config['class'];
73
74            // TODO: Add a factory to create TTM server instances
75            if ( in_array( $class, [ DatabaseTtmServer::class, 'DatabaseTTMServer', 'DatabaseTtmServer' ] ) ) {
76                return new DatabaseTtmServer( $config );
77            }
78
79            return new $class( $config );
80        } elseif ( isset( $config['type'] ) ) {
81            $type = $config['type'];
82            switch ( $type ) {
83                case 'ttmserver':
84                    return new DatabaseTtmServer( $config );
85                case 'remote-ttmserver':
86                    return new RemoteTTMServer( $config );
87                default:
88                    throw new ServiceCreationFailure( "Unknown type for name '$name': $type" );
89            }
90        }
91
92        throw new ServiceCreationFailure( "Invalid configuration for name '$name': type not specified" );
93    }
94
95    public function getDefaultForQuerying(): ReadableTtmServer {
96        if ( $this->default === null ) {
97            return new FakeTTMServer();
98        }
99
100        if ( $this->configs[ $this->default ][ 'writable' ] ?? false ) {
101            throw new InvalidArgumentException(
102                "Default TTM service {$this->default} cannot be write only"
103            );
104        }
105
106        $service = $this->create( $this->default );
107
108        if ( $service instanceof ReadableTtmServer ) {
109            return $service;
110        }
111
112        throw new InvalidArgumentException(
113            "Default TTM service {$this->default} must implement ReadableTtmServer."
114        );
115    }
116
117    /**
118     * Returns writable servers if configured, else returns the default TtmServer else returns null.
119     * @return array [ serverId => WritableTtmServer ]
120     */
121    public function getWritable(): array {
122        $writableServers = $readOnlyServers = [];
123        $ttmServerIds = $this->getNames();
124
125        foreach ( $ttmServerIds as $serverId ) {
126            $isWritable = $this->configs[ $serverId ][ 'writable' ] ?? null;
127
128            if ( $isWritable ) {
129                if ( $serverId === $this->default ) {
130                    throw new InvalidArgumentException(
131                        "Default TTM server {$this->default} cannot be write only"
132                    );
133                }
134
135                $server = $this->create( $serverId );
136                if ( !$server instanceof WritableTtmServer ) {
137                    throw new InvalidArgumentException(
138                        "Server '$serverId' marked writable does not implement WritableTtmServer interface"
139                    );
140                }
141                $writableServers[ $serverId ] = $server;
142            } elseif ( $isWritable === false ) {
143                $readOnlyServers[] = $serverId;
144            }
145        }
146
147        if ( $writableServers ) {
148            return $writableServers;
149        }
150
151        // If there are no writable server, check and use the default server
152        if ( $this->default ) {
153            $defaultTtmServer = $this->create( $this->default );
154
155            if ( $defaultTtmServer instanceof WritableTtmServer ) {
156                if ( !in_array( $this->default, $readOnlyServers ) ) {
157                    $writableServers[ $this->default ] = $defaultTtmServer;
158                }
159            }
160
161            if ( $writableServers ) {
162                return $writableServers;
163            }
164        }
165
166        // Did not find any writable servers.
167        return [];
168    }
169
170    /** Get servers marked as writable */
171    public function getWriteOnly(): array {
172        $ttmServerIds = $this->getNames();
173        $writableServers = [];
174        foreach ( $ttmServerIds as $serverId ) {
175            if ( $this->configs[ $serverId ][ 'writable' ] ?? false ) {
176                $server = $this->create( $serverId );
177                if ( !$server instanceof WritableTtmServer ) {
178                    throw new \InvalidArgumentException(
179                        "Server '$serverId' marked writable does not implement WritableTtmServer interface"
180                    );
181                }
182                $writableServers[ $serverId ] = $server;
183            }
184        }
185
186        return $writableServers;
187    }
188}