Completed
Push — master ( 803e6e...c485df )
by Vladimir
02:40
created

ConversationManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace FondBot\Conversation;
6
7
use FondBot\Channels\Chat;
8
use FondBot\Channels\User;
9
use FondBot\Channels\Channel;
10
use InvalidArgumentException;
11
use Illuminate\Cache\Repository;
12
use FondBot\Events\MessageReceived;
13
use FondBot\Contracts\Conversation\Manager;
14
use FondBot\Contracts\Conversation\Conversable;
15
use Illuminate\Contracts\Foundation\Application;
16
17
class ConversationManager implements Manager
18
{
19
    private $intents = [];
20
    private $fallbackIntent;
21
22
    private $application;
23
    private $cache;
24
25
    private $transitioned = false;
26
27
    private $messageReceived;
28
29 71
    public function __construct(Application $application, Repository $cache)
30
    {
31 71
        $this->application = $application;
32 71
        $this->cache = $cache;
33 71
    }
34
35
    /**
36
     * Register intent.
37
     *
38
     * @param string $class
39
     */
40
    public function registerIntent(string $class): void
41
    {
42
        $this->intents[] = $class;
43
    }
44
45
    /**
46
     * Register fallback intent.
47
     *
48
     * @param string $class
49
     */
50 71
    public function registerFallbackIntent(string $class): void
51
    {
52 71
        $this->fallbackIntent = $class;
53 71
    }
54
55
    /**
56
     * Get all registered intents.
57
     *
58
     * @return array
59
     */
60
    public function getIntents(): array
61
    {
62
        return $this->intents;
63
    }
64
65
    /**
66
     * Match intent by received message.
67
     *
68
     * @param MessageReceived $messageReceived
69
     *
70
     * @return Intent|null
71
     */
72
    public function matchIntent(MessageReceived $messageReceived): ?Intent
73
    {
74
        foreach ($this->intents as $intent) {
75
            /** @var Intent $intent */
76
            $intent = resolve($intent);
0 ignored issues
show
Documentation introduced by
$intent is of type object<FondBot\Conversation\Intent>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
77
78
            foreach ($intent->activators() as $activator) {
79
                if ($activator->matches($messageReceived) && $intent->passesAuthorization($messageReceived)) {
80
                    return $intent;
81
                }
82
            }
83
        }
84
85
        // Otherwise, return fallback intent
86
        return resolve($this->fallbackIntent);
87
    }
88
89
    /**
90
     * Resolve conversation context.
91
     *
92
     * @param Channel $channel
93
     * @param Chat    $chat
94
     * @param User    $user
95
     *
96
     * @return Context|null
97
     */
98
    public function resolveContext(Channel $channel, Chat $chat, User $user): Context
99
    {
100
        $value = $this->cache->get($this->getCacheKeyForContext($channel, $chat, $user), [
101
            'chat' => $chat,
102
            'user' => $user,
103
            'intent' => null,
104
            'interaction' => null,
105
        ]);
106
107
        $context = new Context($channel, $chat, $user);
108
109
        if ($value['intent'] !== null) {
110
            $context->setIntent(resolve($value['intent']));
111
        }
112
113
        if ($value['interaction'] !== null) {
114
            $context->setInteraction(resolve($value['interaction']));
115
        }
116
117
        // Bind resolved instance to the container
118
        $this->application->instance('fondbot.conversation.context', $context);
119
120
        return $context;
121
    }
122
123
    /**
124
     * Save context.
125
     *
126
     * @param Context $context
127
     */
128
    public function saveContext(Context $context): void
129
    {
130
        $this->cache->forever(
131
            $this->getCacheKeyForContext($context->getChannel(), $context->getChat(), $context->getUser()),
132
            $context->toArray()
133
        );
134
    }
135
136
    /**
137
     * Flush context.
138
     *
139
     * @param Context $context
140
     */
141
    public function flushContext(Context $context): void
142
    {
143
        $this->cache->forget(
144
            $this->getCacheKeyForContext($context->getChannel(), $context->getChat(), $context->getUser())
145
        );
146
    }
147
148
    /**
149
     * Get current context.
150
     *
151
     * @return Context|null
152
     */
153 71
    public function getContext(): ?Context
154
    {
155 71
        if (!$this->application->has('fondbot.conversation.context')) {
156 71
            return null;
157
        }
158
159 13
        return $this->application->get('fondbot.conversation.context');
160
    }
161
162
    /**
163
     * Define received message.
164
     *
165
     * @param MessageReceived $messageReceived
166
     */
167
    public function setReceivedMessage(MessageReceived $messageReceived): void
168
    {
169
        $this->messageReceived = $messageReceived;
170
    }
171
172
    /**
173
     * Mark conversation as transitioned.
174
     */
175
    public function markAsTransitioned(): void
176
    {
177
        $this->transitioned = true;
178
    }
179
180
    /**
181
     * Determine if conversation has been transitioned.
182
     *
183
     * @return bool
184
     */
185
    public function transitioned(): bool
186
    {
187
        return $this->transitioned;
188
    }
189
190
    /**
191
     * Start conversation.
192
     *
193
     * @param Conversable $conversable
194
     */
195
    public function converse(Conversable $conversable): void
196
    {
197
        if ($conversable instanceof Intent) {
198
            context()->setIntent($conversable)->setInteraction(null);
199
        }
200
201
        $conversable->handle($this->messageReceived);
202
    }
203
204
    /**
205
     * Jump to interaction.
206
     *
207
     * @param string $conversable
208
     */
209
    public function transition(string $conversable): void
210
    {
211
        /** @var Interaction $instance */
212
        $instance = resolve($conversable);
213
214
        if (!$instance instanceof Conversable) {
215
            throw new InvalidArgumentException('Invalid conversable `'.$conversable.'`');
216
        }
217
218
        $this->converse($instance, $this->messageReceived);
0 ignored issues
show
Unused Code introduced by
The call to ConversationManager::converse() has too many arguments starting with $this->messageReceived.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
219
        $this->markAsTransitioned();
220
    }
221
222
    /**
223
     * Restart current dialog.
224
     *
225
     * @param Conversable $conversable
226
     */
227
    public function restart(Conversable $conversable): void
228
    {
229
        if ($conversable instanceof Intent) {
230
            $this->markAsTransitioned();
231
        }
232
233
        $this->converse($conversable);
234
235
        if ($conversable instanceof Interaction) {
236
            $this->markAsTransitioned();
237
        }
238
    }
239
240 71
    public function __destruct()
241
    {
242 71
        $context = $this->getContext();
243
244 71
        if ($context === null) {
245 71
            return;
246
        }
247
248
        // Close session if conversation has not been transitioned
249
        if (!$this->transitioned()) {
250
            $this->flushContext($context);
251
        }
252
253
        // Save context if exists
254
        if ($this->transitioned() && $context = context()) {
255
            $this->saveContext($context);
256
        }
257
    }
258
259
    private function getCacheKeyForContext(Channel $channel, Chat $chat, User $user): string
260
    {
261
        return implode('.', ['context', $channel->getName(), $chat->getId(), $user->getId()]);
262
    }
263
}
264