BotState   A
last analyzed

Complexity

Total Complexity 4

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 17.65%

Importance

Changes 0
Metric Value
wmc 4
lcom 1
cbo 1
dl 0
loc 102
ccs 3
cts 17
cp 0.1765
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getStatus() 0 13 2
A setStatus() 0 7 1
1
<?php
2
3
/*
4
 * This file is part of the PhpBotFramework.
5
 *
6
 * PhpBotFramework is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, version 3.
9
 *
10
 * PhpBotFramework is distributed in the hope that it will be useful, but
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace PhpBotFramework\Utilities;
20
21
/**
22
 * \addtogroup Modules
23
 * @{
24
 */
25
use PhpBotFramework\Exceptions\BotException;
26
27
use PhpBotFramework\BasicBot;
28
29
/**
30
 * \class BotState
31
 * \brief Handle users status.
32
 */
33
class BotState
34
{
35
    private $bot;
36
37
    /** \brief Current status of the user. */
38
    public $status;
39
40
    /**
41
     * \addtogroup State
42
     * \brief Create a state-based bot using these methods.
43
     * \details The bot will answer in different ways based on its internal state.
44
     *
45
     * Below an example where we save user's credentials using bot states:
46
     *
47
     *     <?php
48
     *
49
     *     // Include the framework
50
     *     require './vendor/autoload.php';
51
     *
52
     *     // Define bot state
53
     *     define("SEND_USERNAME", 1);
54
     *     define("SEND_PASSWORD", 2);
55
     *
56
     *     // Create the class for the bot that will handle login
57
     *     class LoginBot extends PhpBotFramework\Bot {
58
     *
59
     *         // Add the function for processing messages
60
     *         protected function processMessage($message) {
61
     *             switch($this->getStatus()) {
62
     *                 case SEND_USERNAME:
63
     *                     $this->sendMessage("Please, send your password.");
64
     *
65
     *                     // Update the bot state
66
     *                     $this->setStatus(SEND_PASSWORD);
67
     *                     break;
68
     *
69
     *                 // Or if we are expecting a password from the user
70
     *                 case SEND_PASSWORD:
71
     *                     $this->sendMessage("The registration is complete");
72
     *                     break;
73
     *             }
74
     *         }
75
     *
76
     *     }
77
     *
78
     *     $bot = new LoginBot("token");
79
     *
80
     *     $bot->redis = new Redis();
81
     *     $bot->redis->connect('127.0.0.1');
82
     *
83
     *     // Create the answer to the <code>/start</code> command
84
     *     $start_closure = function($bot, $message) {
85
     *         $bot->sendMessage("Please, send your username.");
86
     *         $bot->setStatus(SEND_USERNAME);
87
     *     };
88
     *
89
     *     $bot->addMessageCommand("start", $start_closure);
90
     *     $bot->getUpdatesLocal();
91
     * @{
92
     */
93
94 1
    public function __construct(BasicBot &$bot)
95
    {
96 1
        $this->bot = $bot;
97 1
    }
98
99
    /**
100
     * \brief Get current user status from Redis and set it in status variable.
101
     * \details Throws an exception if the Redis connection is missing.
102
     * @param int $default_status <i>Optional</i>. The default status to return
103
     * if there is no status for the current user.
104
     * @return int The status for the current user, $default_status if missing.
105
     */
106
    public function getStatus(int $default_status = -1) : int
107
    {
108
        $chat_id = $this->bot->getChatID();
109
        $redis = $this->bot->getRedis();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PhpBotFramework\BasicBot as the method getRedis() does only exist in the following sub-classes of PhpBotFramework\BasicBot: DonateBot, EchoBot, PhpBotFramework\Bot, PhpBotFramework\Test\TestBot, WhoAmIBot. 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...
110
        if ($redis->exists($chat_id . ':status')) {
111
            $this->status = $redis->get($chat_id . ':status');
112
            return $this->status;
113
        }
114
115
        $redis->set($chat_id . ':status', $default_status);
116
        $this->status = $default_status;
117
        return $this->status;
118
    }
119
120
    /**
121
     * \brief Set the status of the bot in both Redis and $status.
122
     * \details Throws an exception if the Redis connection is missing.
123
     * @param int $status The new status of the bot.
124
     */
125
    public function setStatus(int $status)
126
    {
127
        $redis = $this->bot->getRedis();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PhpBotFramework\BasicBot as the method getRedis() does only exist in the following sub-classes of PhpBotFramework\BasicBot: DonateBot, EchoBot, PhpBotFramework\Bot, PhpBotFramework\Test\TestBot, WhoAmIBot. 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...
128
        $redis->set($this->bot->getChatID() . ':status', $status);
129
130
        $this->status = $status;
131
    }
132
133
    /** @} */
134
}
135