Completed
Push — master ( 8d6c09...33443d )
by Sam
05:30 queued 40s
created

PersistenceManager   C

Complexity

Total Complexity 25

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 23

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 25
c 3
b 1
f 1
lcom 2
cbo 23
dl 0
loc 288
rs 5.5

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B onInstanceSeen() 0 24 2
B onConnectionSeen() 0 26 3
A onUserSeen() 0 14 2
A onChannelSeen() 0 14 2
B onSubscriptionSeen() 0 40 5
B onSubscriptionStateChange() 0 35 4
A hasInstance() 0 4 1
A hasConnection() 0 5 1
A hasUser() 0 4 1
A hasChannel() 0 5 1
A hasSubscription() 0 14 2
1
<?php
2
3
namespace Jalle19\StatusManager;
4
5
use Jalle19\StatusManager\Database;
6
use Jalle19\StatusManager\Database\Channel;
7
use Jalle19\StatusManager\Database\ChannelQuery;
8
use Jalle19\StatusManager\Database\Connection;
9
use Jalle19\StatusManager\Database\ConnectionQuery;
10
use Jalle19\StatusManager\Database\InstanceQuery;
11
use Jalle19\StatusManager\Database\Subscription;
12
use Jalle19\StatusManager\Database\SubscriptionQuery;
13
use Jalle19\StatusManager\Database\User;
14
use Jalle19\StatusManager\Database\UserQuery;
15
use Jalle19\StatusManager\Subscription\StateChange;
16
use Jalle19\tvheadend\model\ConnectionStatus;
17
use Jalle19\tvheadend\model\SubscriptionStatus;
18
use Jalle19\tvheadend\Tvheadend;
19
use Psr\Log\LoggerInterface;
20
21
/**
22
 * Handles persisting of objects to the database
23
 *
24
 * @package   Jalle19\StatusManager
25
 * @copyright Copyright &copy; Sam Stenvall 2015-
26
 * @license   https://www.gnu.org/licenses/gpl.html The GNU General Public License v2.0
27
 */
28
class PersistenceManager
29
{
30
31
	/**
32
	 * @var LoggerInterface
33
	 */
34
	private $_logger;
35
36
37
	/**
38
	 * @param LoggerInterface $logger
39
	 */
40
	public function __construct(LoggerInterface $logger)
41
	{
42
		$this->_logger = $logger;
43
	}
44
45
46
	/**
47
	 * @param Tvheadend $instance
48
	 *
49
	 * @throws \Propel\Runtime\Exception\PropelException
50
	 */
51
	public function onInstanceSeen(Tvheadend $instance)
52
	{
53
		if ($this->hasInstance($instance))
54
			return;
55
56
		$instanceModel = new Database\Instance();
57
		$instanceModel->setPrimaryKey($instance->getHostname());
58
		$instanceModel->save();
59
60
		$this->_logger->info('Stored new instance {instanceName}', [
61
			'instanceName' => $instance->getHostname(),
62
		]);
63
64
		// Create a special user for eventual DVR subscriptions
65
		$user = new User();
66
		$user->setInstance($instanceModel);
67
		$user->setName(User::NAME_DVR);
68
		$user->save();
69
70
		$this->_logger->info('Stored new special user (instance: {instanceName}, user: {userName})', [
71
			'instanceName' => $instance->getHostname(),
72
			'userName'     => $user->getName(),
73
		]);
74
	}
75
76
77
	/**
78
	 * @param string           $instanceName
79
	 * @param ConnectionStatus $connectionStatus
80
	 */
81
	public function onConnectionSeen($instanceName, ConnectionStatus $connectionStatus)
82
	{
83
		if ($this->hasConnection($instanceName, $connectionStatus))
84
			return;
85
86
		$user = null;
87
88
		// Find the user object when applicable
89
		if (!$connectionStatus->isAnonymous())
90
		{
91
			$this->onUserSeen($instanceName, $connectionStatus->user);
92
93
			$user = UserQuery::create()->filterByInstanceName($instanceName)->filterByName($connectionStatus->user)
94
			                 ->findOne();
95
		}
96
97
		$connection = new Connection();
98
		$connection->setInstanceName($instanceName)->setPeer($connectionStatus->peer)
99
		           ->setUser($user)
100
		           ->setStarted($connectionStatus->started)->setType($connectionStatus->type)->save();
101
102
		$this->_logger->info('Stored new connection (instance: {instanceName}, peer: {peer})', [
103
			'instanceName' => $instanceName,
104
			'peer'         => $connectionStatus->peer,
105
		]);
106
	}
107
108
109
	/**
110
	 * @param string $instanceName
111
	 * @param string $userName
112
	 *
113
	 * @throws \Propel\Runtime\Exception\PropelException
114
	 */
115
	public function onUserSeen($instanceName, $userName)
116
	{
117
		if ($this->hasUser($instanceName, $userName))
118
			return;
119
120
		$user = new User();
121
		$user->setInstanceName($instanceName)->setName($userName);
122
		$user->save();
123
124
		$this->_logger->info('Stored new user (instance: {instanceName}, username: {userName})', [
125
			'instanceName' => $instanceName,
126
			'userName'     => $userName,
127
		]);
128
	}
129
130
131
	/**
132
	 * @param string $instanceName
133
	 * @param string $channelName
134
	 *
135
	 * @throws \Propel\Runtime\Exception\PropelException
136
	 */
137
	public function onChannelSeen($instanceName, $channelName)
138
	{
139
		if ($this->hasChannel($instanceName, $channelName))
140
			return;
141
142
		$channel = new Channel();
143
		$channel->setInstanceName($instanceName)->setName($channelName);
144
		$channel->save();
145
146
		$this->_logger->info('Stored new channel (instance: {instanceName}, name: {channelName})', [
147
			'instanceName' => $instanceName,
148
			'channelName'  => $channelName,
149
		]);
150
	}
151
152
153
	/**
154
	 * @param string             $instanceName
155
	 * @param SubscriptionStatus $status
156
	 *
157
	 * @throws \Propel\Runtime\Exception\PropelException
158
	 */
159
	public function onSubscriptionSeen($instanceName, SubscriptionStatus $status)
160
	{
161
		// Ignore EPG grabber subscriptions
162
		if ($status->getType() === SubscriptionStatus::TYPE_EPGGRAB)
163
			return;
164
165
		// Determine the username to store for the subscription
166
		$username = $status->username;
167
168
		switch ($status->getType())
169
		{
170
			case SubscriptionStatus::TYPE_RECORDING:
171
				$username = 'dvr';
172
				break;
173
		}
174
175
		// Get the instance, user and channel
176
		$instance = InstanceQuery::create()->findPk($instanceName);
177
		$user     = UserQuery::create()->filterByInstance($instance)->filterByName($username)->findOne();
178
179
		// Ensure the channel exists
180
		$this->onChannelSeen($instanceName, $status->channel);
181
		$channel = ChannelQuery::create()->filterByInstance($instance)->filterByName($status->channel)->findOne();
182
183
		if ($this->hasSubscription($instance, $user, $channel, $status))
184
			return;
185
186
		$subscription = new Subscription();
187
		$subscription->setInstance($instance)->setUser($user)->setChannel($channel)
188
		             ->setSubscriptionId($status->id)->setStarted($status->start)->setTitle($status->title)
189
		             ->setService($status->service);
190
		$subscription->save();
191
192
		$this->_logger->info('Stored new subscription (instance: {instanceName}, user: {userName}, channel: {channelName})',
193
			[
194
				'instanceName' => $instanceName,
195
				'userName'     => $user !== null ? $user->getName() : 'N/A',
196
				'channelName'  => $channel->getName(),
197
			]);
198
	}
199
200
201
	/**
202
	 * @param string      $instanceName
203
	 * @param StateChange $stateChange
204
	 */
205
	public function onSubscriptionStateChange($instanceName, StateChange $stateChange)
206
	{
207
		// We only need to persist subscription stops
208
		if ($stateChange->getState() === StateChange::STATE_SUBSCRIPTION_STARTED)
209
			return;
210
211
		// Find the latest matching subscription
212
		$subscription = SubscriptionQuery::create()->filterByInstanceName($instanceName)
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Propel\Runtime\ActiveQuery\Criteria as the method findOne() does only exist in the following sub-classes of Propel\Runtime\ActiveQuery\Criteria: Jalle19\StatusManager\Database\Base\ChannelQuery, Jalle19\StatusManager\Da...se\Base\ConnectionQuery, Jalle19\StatusManager\Database\Base\InstanceQuery, Jalle19\StatusManager\Da...\Base\SubscriptionQuery, Jalle19\StatusManager\Database\Base\UserQuery, Jalle19\StatusManager\Database\ChannelQuery, Jalle19\StatusManager\Database\ConnectionQuery, Jalle19\StatusManager\Database\InstanceQuery, Jalle19\StatusManager\Database\SubscriptionQuery, Jalle19\StatusManager\Database\UserQuery, Propel\Runtime\ActiveQuery\ModelCriteria. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
213
		                                 ->filterBySubscriptionId($stateChange->getSubscriptionId())
214
		                                 ->addDescendingOrderByColumn('started')->findOne();
215
216
		if ($subscription === null)
217
		{
218
			$this->_logger->warning('Got subscription stop without a matching start (instance: {instanceName}, subscription: {subscriptionId})',
219
				[
220
					'instanceName'   => $instanceName,
221
					'subscriptionId' => $stateChange->getSubscriptionId(),
222
				]);
223
224
			return;
225
		}
226
227
		$subscription->setStopped(new \DateTime());
228
		$subscription->save();
229
230
		$user    = $subscription->getUser();
231
		$channel = $subscription->getChannel();
232
233
		$this->_logger->info('Stored subscription stop (instance: {instanceName}, user: {userName}, channel: {channelName})',
234
			[
235
				'instanceName' => $instanceName,
236
				'userName'     => $user !== null ? $user->getName() : 'N/A',
237
				'channelName'  => $channel->getName(),
238
			]);
239
	}
240
241
242
	/**
243
	 * @param Tvheadend $instance
244
	 *
245
	 * @return bool whether the instance exists in the database
246
	 */
247
	private function hasInstance(Tvheadend $instance)
248
	{
249
		return InstanceQuery::create()->findPk($instance->getHostname()) !== null;
250
	}
251
252
253
	/**
254
	 * @param                  $instanceName
255
	 * @param ConnectionStatus $connectionStatus
256
	 *
257
	 * @return bool whether the connection exists in the database
258
	 */
259
	private function hasConnection($instanceName, ConnectionStatus $connectionStatus)
260
	{
261
		return ConnectionQuery::create()->filterByInstanceName($instanceName)->filterByPeer($connectionStatus->peer)
262
		                      ->filterByStarted($connectionStatus->started)->findOne() !== null;
263
	}
264
265
266
	/**
267
	 * @param string $instanceName
268
	 * @param string $userName
269
	 *
270
	 * @return bool
271
	 */
272
	private function hasUser($instanceName, $userName)
273
	{
274
		return UserQuery::create()->filterByInstanceName($instanceName)->filterByName($userName)->findOne() !== null;
275
	}
276
277
278
	/**
279
	 * @param string $instanceName
280
	 * @param string $channelName
281
	 *
282
	 * @return bool
283
	 */
284
	private function hasChannel($instanceName, $channelName)
285
	{
286
		return ChannelQuery::create()->filterByInstanceName($instanceName)->filterByName($channelName)
287
		                   ->findOne() !== null;
288
	}
289
290
291
	/**
292
	 * @param Database\Instance  $instance
293
	 * @param                    $user
294
	 * @param Channel            $channel
295
	 * @param SubscriptionStatus $subscription
296
	 *
297
	 * @return bool
298
	 * @throws \Propel\Runtime\Exception\PropelException
299
	 */
300
	private function hasSubscription(
301
		Database\Instance $instance,
302
		$user,
303
		Channel $channel,
304
		SubscriptionStatus $subscription
305
	) {
306
		// Not all subscriptions are tied to a user
307
		$userId = $user !== null ? $user->getId() : null;
308
309
		return SubscriptionQuery::create()->filterByInstance($instance)->filterByUserId($userId)
310
		                        ->filterByChannel($channel)
311
		                        ->filterBySubscriptionId($subscription->id)->filterByStarted($subscription->start)
312
		                        ->findOne() !== null;
313
	}
314
315
}
316