Localization::getLanguageRedis()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 10
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 1
crap 6
1
<?php
2
/*
3
 * This file is part of the PhpBotFramework.
4
 *
5
 * PhpBotFramework is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as
7
 * published by the Free Software Foundation, version 3.
8
 *
9
 * PhpBotFramework is distributed in the hope that it will be useful, but
10
 * WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
namespace PhpBotFramework\Localization;
19
20
use PhpBotFramework\BasicBot;
21
use PhpBotFramework\Exceptions\BotException;
22
23
class Localization
24
{
25
    use File,
26
        LocalizedString;
27
    /**
28
     * \addtogroup Localization Localization
29
     * \brief Create a localized bot.
30
     * \details Using both a sql database and a redis database you can develop a localized bot with just a small impact on the performance.
31
     * By default the sql database will store the language permanently in a table which name is defined in $user_table.
32
     * The redis database will cache the language for the chat_id for a variable time.
33
     * You can use only the sql database if a redis database is not accessible.
34
     * These methods will treat groups as User so they will be stored in the table as normal user does.
35
     * @{
36
     */
37
38
    /** \brief Reference to the bot. */
39
    protected $bot;
40
41
    /** \brief Table containing bot users data into database. */
42
    public $user_table = 'User';
43
44
    /** \brief Name of the column that represents the user ID into database */
45
    public $id_column = 'chat_id';
46
47
    /** \brief Current user/group language. */
48
    public $language;
49
50 1
    public function __construct(BasicBot &$bot)
51
    {
52 1
        $this->bot = $bot;
53 1
    }
54
55
    /**
56
     * \brief Get current user's language from the database, and set it in $language.
57
     * @return string Language set for the current user, throw error if there is language is not set for the user.
58
     */
59
    public function getLanguageDatabase() : string
60
    {
61
        $pdo = $this->bot->getPDO();
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 getPDO() 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...
62
63
        // Get the language from the bot
64
        $sth = $pdo->prepare('SELECT language FROM ' . $this->user_table . ' WHERE '
65
                                                           . $this->id_column . ' = :chat_id');
66
67
        $chat_id = $this->bot->getChatID();
68
        $sth->bindParam(':chat_id', $chat_id);
69
70
        try {
71
            $sth->execute();
72
        } catch (\PDOException $e) {
73
            throw new BotException($e->getMessage() . "/n" . $e->getLine());
74
        }
75
76
        $row = $sth->fetch();
77
78
        if (isset($row['language'])) {
79
            $this->language = $row['language'];
80
            return $row['language'];
81
        }
82
83
        throw new BotException("Could not load language from database for user: $chat_id");
84
    }
85
86
    /**
87
     * \brief Set the current user language in database and internally.
88
     * \details Save it into database first.
89
     * @param string $language The language to set.
90
     * @return bool On sucess, return true, throws exception otherwise.
91
     */
92
    public function setLanguageDatabase(string $language) : bool
93
    {
94
        $pdo = $this->bot->getPDO();
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 getPDO() 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...
95
96
        // Update the language in the database
97
        $sth = $pdo->prepare('UPDATE ' . $this->user_table . ' SET language = :language WHERE '
98
                                             . $this->id_column . ' = :id');
99
        $sth->bindParam(':language', $language);
100
101
        $chat_id = $this->bot->getChatID();
102
        $sth->bindParam(':id', $chat_id);
103
104
        try {
105
            $sth->execute();
106
        } catch (\PDOException $e) {
107
            throw new BotException($e->getMessage());
108
        }
109
110
        $this->language = $language;
111
112
        return true;
113
    }
114
115
    /**
116
     * \brief Get current user language from Redis (as a cache) and set it in language.
117
     * \details Using Redis as cache, check for the language. On failure, get the language
118
     * from the database and store it (with default expiring time of one day) in Redis.
119
     *
120
     * It also change $language parameter of the bot to the language returned.
121
     * @param int $expiring_time <i>Optional</i>. Set the expiring time for the language on redis each time it is took from the sql database.
122
     * @return string Language for the current user, $default_language on errors.
123
     */
124
    public function getLanguageRedis(int $expiring_time = 86400) : string
125
    {
126
        $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...
127
        $chat_id = $this->bot->getChatID();
128
129
        // Check if the language exists on Redis
130
        if ($redis->exists($this->bot->getChatID() . ':language')) {
131
            $this->language = $redis->get($chat_id . ':language');
132
            return $this->language;
133
        }
134
135
        // Set the value from the database
136
        $redis->setEx(
137
            $this->bot->getChatID() . ':language',
138
            $expiring_time,
139
            $this->getLanguageDatabase()
140
        );
141
        return $this->language;
142
    }
143
144
    /**
145
     * \brief Set the current user language in both Redis, database and internally.
146
     * \details Save it into database first, then create the expiring key on Redis.
147
     * @param string $language The language to set.
148
     * @param int $expiring_time <i>Optional</i>. Time for the language key in redis to expire.
149
     * @return bool On sucess, return true, throws exception otherwise.
150
     */
151
    public function setLanguageRedis(string $language, int $expiring_time = 86400) : bool
152
    {
153
        $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...
154
155
        // If we could successfully set the language in the database
156
        if ($this->setLanguageDatabase($language)) {
157
            // Set the language in Redis
158
            $redis->setEx($this->bot->getChatID() . ':language', $expiring_time, $language);
159
            return true;
160
        }
161
162
        return false;
163
    }
164
165
    /** @} */
166
}
167