Completed
Push — master ( ebdf7a...2dba39 )
by Vladimir
05:12
created

FondBot::matchIntent()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 10
nc 6
nop 1
dl 0
loc 23
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace FondBot;
6
7
use Closure;
8
use SplFileInfo;
9
use ReflectionClass;
10
use Illuminate\Support\Str;
11
use FondBot\Contracts\Activator;
12
use FondBot\Conversation\Intent;
13
use FondBot\Events\MessageReceived;
14
use Symfony\Component\Finder\Finder;
15
use FondBot\Conversation\FallbackIntent;
16
17
class FondBot
18
{
19
    /**
20
     * The registered drivers.
21
     *
22
     * @var array
23
     */
24
    private $drivers = [];
25
26
    /**
27
     * The registered channels.
28
     *
29
     * @var array
30
     */
31
    private $channels = [];
32
33
    /**
34
     * The registered intents.
35
     *
36
     * @var array
37
     */
38
    private $intents = [];
39
40
    /**
41
     * Fallback intent.
42
     *
43
     * @var string
44
     */
45
    private $fallbackIntent = FallbackIntent::class;
46
47
    /**
48
     * Context time to live in seconds.
49
     *
50
     * @var int
51
     */
52
    private $contextTtl;
53
54
    /**
55
     * Current channel.
56
     *
57
     * @var Channel
58
     */
59
    public static $currentChannel;
60
61
    public function __construct(array $config)
62
    {
63
        $this->contextTtl = $config['context_ttl'];
64
        $this->registerChannels($config['channels']);
65
        $this->intentsIn($config['intents_path']);
66
        $this->fallbackIntent = $config['fallback_intent'];
67
    }
68
69
    /**
70
     * Get the current FondBot version.
71
     *
72
     * @return string
73
     */
74
    public static function version(): string
75
    {
76
        return '4.0.0';
77
    }
78
79
    /**
80
     * Register a driver.
81
     *
82
     * @param string $name
83
     * @param Closure $callback
84
     */
85
    public function addDriver(string $name, Closure $callback): void
86
    {
87
        $this->drivers[$name] = $callback;
88
    }
89
90
    /**
91
     * Get all registered drivers.
92
     *
93
     * @return array
94
     */
95
    public function drivers(): array
96
    {
97
        return $this->drivers;
98
    }
99
100
    /**
101
     * Get all registered channels.
102
     *
103
     * @return array
104
     */
105
    public function channels(): array
106
    {
107
        return $this->channels;
108
    }
109
110
    /**
111
     * Get channel by name.
112
     *
113
     * @param string $name
114
     * @return Channel
115
     */
116
    public function channel(string $name): Channel
117
    {
118
        return $this->channels[$name];
119
    }
120
121
    /**
122
     * Get all registered intents.
123
     *
124
     * @return array
125
     */
126
    public function intents(): array
127
    {
128
        return $this->intents;
129
    }
130
131
    /**
132
     * Match intent by received message.
133
     *
134
     * @param MessageReceived $messageReceived
135
     *
136
     * @return Intent|null
137
     */
138
    public function matchIntent(MessageReceived $messageReceived): Intent
139
    {
140
        foreach ($this->intents as $intent) {
141
            /** @var Intent $intent */
142
            $intent = resolve($intent);
0 ignored issues
show
Bug introduced by
$intent of type FondBot\Conversation\Intent is incompatible with the type string expected by parameter $name of resolve(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
            $intent = resolve(/** @scrutinizer ignore-type */ $intent);
Loading history...
143
144
            foreach ($intent->activators() as $activator) {
145
                if (!$intent->passesAuthorization($messageReceived)) {
146
                    continue;
147
                }
148
149
                if ($activator instanceof Closure && value($activator($messageReceived)) === true) {
150
                    return $intent;
151
                }
152
153
                if ($activator instanceof Activator && $activator->matches($messageReceived)) {
154
                    return $intent;
155
                }
156
            }
157
        }
158
159
        // Otherwise, return fallback intent
160
        return resolve($this->fallbackIntent);
0 ignored issues
show
Bug Best Practice introduced by
The expression return resolve($this->fallbackIntent) could return the type Illuminate\Foundation\Application which is incompatible with the type-hinted return FondBot\Conversation\Intent. Consider adding an additional type-check to rule them out.
Loading history...
161
    }
162
163
    /**
164
     * Register the given channels.
165
     * @param array $channels
166
     */
167
    private function registerChannels(array $channels): void
168
    {
169
        $this->channels = collect($channels)
170
            ->mapWithKeys(function (array $parameters, string $name) {
171
                $driverCallback = $this->drivers[$parameters['driver']];
172
173
                /** @var Driver $driver */
174
                $driver = $driverCallback();
175
                $driver->initialize(collect($parameters)->except('driver'));
176
177
                return [
178
                    $name => new Channel($name, $driver, $parameters['webhook-secret'] ?? null),
179
                ];
180
            })
181
            ->toArray();
182
    }
183
184
    /**
185
     * Register all of the intent classes in the given directories.
186
     *
187
     * @param array $directories
188
     */
189
    private function intentsIn(array $directories): void
190
    {
191
        $namespace = app()->getNamespace();
0 ignored issues
show
introduced by
The method getNamespace() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

191
        $namespace = app()->/** @scrutinizer ignore-call */ getNamespace();
Loading history...
192
193
        $intents = [];
194
195
        /** @var SplFileInfo[] $files */
196
        $files = (new Finder())->in($directories)->files();
197
198
        foreach ($files as $file) {
199
            $file = $namespace.str_replace(
200
                    ['/', '.php'],
201
                    ['\\', ''],
202
                    Str::after($file->getPathname(), app_path().DIRECTORY_SEPARATOR)
203
                );
204
205
            if (is_subclass_of($file, Intent::class) && !(new ReflectionClass($file))->isAbstract()) {
206
                $intents[] = $file;
207
            }
208
        }
209
210
        $this->intents = $intents;
211
    }
212
}
213