TokenRepository   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Test Coverage

Coverage 97.5%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 47
c 3
b 0
f 0
dl 0
loc 130
ccs 39
cts 40
cp 0.975
rs 10
wmc 15

6 Methods

Rating   Name   Duplication   Size   Complexity  
A delete() 0 10 3
A pull() 0 8 1
A __construct() 0 5 1
A push() 0 14 2
A verify() 0 26 4
A send() 0 21 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Wearesho\Yii\Repositories;
6
7
use Wearesho\Yii\Entities\TokenableEntity;
8
use Wearesho\Yii\Exceptions\DeliveryLimitReachedException;
9
use Wearesho\Yii\Exceptions\InvalidRecipientException;
10
use Wearesho\Yii\Exceptions\InvalidTokenException;
11
12
use Wearesho\Yii\Interfaces\TokenableEntityInterface;
13
use Wearesho\Yii\Interfaces\TokenGeneratorInterface;
14
use Wearesho\Yii\Interfaces\TokenInterface;
15
use Wearesho\Yii\Interfaces\TokenRecordInterface;
16
use Wearesho\Yii\Interfaces\TokenRepositoryInterface;
17
use Wearesho\Yii\Interfaces\TokenRepositoryConfigInterface;
18
19
use Horat1us\Yii\Validation;
20
use Wearesho\Delivery;
21
use Wearesho\Yii\Models\Token;
22
23
class TokenRepository implements TokenRepositoryInterface
24
{
25
    public function __construct(
26
        protected TokenRepositoryConfigInterface $config,
27
        protected TokenGeneratorInterface        $generator,
28
        protected Delivery\ServiceInterface      $deliveryService
29
    ) {
30
    }
31
32
    /**
33
     * Creating token (for example, when we receive first-stage data)
34
     * Will increase sending counter
35
     *
36
     * @param TokenableEntityInterface $entity
37
     * @return TokenInterface|TokenRecordInterface
38
     * @throws Validation\Failure
39
     */
40
    public function push(TokenableEntityInterface $entity): TokenInterface
41
    {
42
        $record = $this->pull($entity);
43
        if (!$record instanceof TokenRecordInterface) {
44
            $record = new Token();
45 13
            $record->type = $entity->getTokenType();
46
            $record->setRecipient($entity->getRecipient());
47
            $record->setToken($this->generator->getToken());
48
        }
49
50
        $record->setData($entity->getTokenData());
51 13
        Validation\Exception::saveOrThrow($record);
52 13
53 13
        return $record;
54 13
    }
55 13
56
    /**
57
     * Creating and sending token
58
     * Internal should use push() method
59
     *
60
     * @param TokenableEntityInterface $entity
61
     * @throws DeliveryLimitReachedException
62
     * @throws Delivery\Exception
63
     * @throws Validation\Failure
64
     * @todo: preventing two write operations (update+update, insert+update)
65 6
     *
66
     */
67 6
    public function send(TokenableEntityInterface $entity): void
68 6
    {
69 6
        $token = $this->push($entity);
70
        if ($token->getDeliveryCount() >= $this->config->getDeliveryLimit()) {
71 6
            throw new DeliveryLimitReachedException(
72
                $token->getDeliveryCount(),
73 6
                $this->config->getExpirePeriod()
74 6
            );
75
        }
76
77 6
        $entityWithToken = new TokenableEntity(
78 6
            $entity->getRecipient(),
79
            str_replace('{token}', $token->getToken(), $entity->getText()),
80 6
            $entity->getTokenType(),
81
            $entity->getTokenData()
82
        );
83
        $deliveryResult = $this->deliveryService->send($entityWithToken);
84
85
        if ($token instanceof TokenRecordInterface && $deliveryResult->status()->isSuccess()) {
86
            $token->increaseDeliveryCount();
87
            Validation\Exception::saveOrThrow($token);
88
        }
89
    }
90
91
    /**
92
     * Pulling active token to process it (for example, sending sms)
93
     */
94 1
    public function pull(TokenableEntityInterface $entity): ?TokenRecordInterface
95
    {
96 1
        /** @noinspection PhpIncompatibleReturnTypeInspection */
97 1
        return Token::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return Wearesho\Yii\Mode...>getRecipient())->one() also could return the type array|yii\db\ActiveRecord which is incompatible with the return type mandated by Wearesho\Yii\Interfaces\...sitoryInterface::pull() of Wearesho\Yii\Interfaces\TokenInterface|null.
Loading history...
98 1
            ->andWhere(['=', 'token.type', $entity->getTokenType()])
99 1
            ->notExpired($this->config->getExpirePeriod())
100 1
            ->whereRecipient($entity->getRecipient())
101
            ->one();
102
    }
103
104 1
    /**
105
     * Will return token with data to process registration if token valid
106 1
     * Or throw one of exceptions
107 1
     * Should delete token from storage if token valid to prevent double validation for single token
108 1
     *
109
     * @return TokenInterface
110 1
     * @throws DeliveryLimitReachedException
111
     * @throws InvalidRecipientException
112
     * @throws InvalidTokenException
113
     * @throws Validation\Failure
114
     */
115
    public function verify(TokenableEntityInterface $entity, string $token): TokenInterface
116
    {
117
        $record = $this->pull($entity);
118 12
119
        if (!$record instanceof TokenRecordInterface) {
120 12
            throw new InvalidRecipientException($entity->getRecipient());
121 12
        }
122 12
123 12
        $record->increaseVerifyCount();
124
125
        Validation\Exception::saveOrThrow($record);
126
127
        if ($this->config->getVerifyLimit() < $record->getVerifyCount()) {
128
            throw new DeliveryLimitReachedException(
129
                $record->getVerifyCount(),
130
                $this->config->getExpirePeriod()
131
            );
132
        }
133
134
        if ($record->getToken() !== $token) {
135
            throw new InvalidTokenException($token);
136
        }
137
138
        $record->delete();
139 7
140
        return $record;
141 7
    }
142
143 7
    public function delete(TokenableEntityInterface $entity): void
144 2
    {
145
        $record = $this->pull($entity);
146
147 5
        if (!$record instanceof TokenRecordInterface) {
148
            throw new InvalidRecipientException($entity->getRecipient());
149 5
        }
150
151 5
        if ($record instanceof Token) {
152
            $record->delete();
153
        }
154
    }
155
}
156