Failed Conditions
Push — master ( dd5c39...c513e8 )
by Adrien
09:16
created

AccountRepository::clearCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Repository;
6
7
use Application\DBAL\Types\AccountTypeType;
8
use Application\Model\Account;
9
use Application\Model\User;
10
use Money\Money;
11
12
class AccountRepository extends AbstractRepository implements LimitedAccessSubQueryInterface
13
{
14
    private const PARENT_ACCOUNT_ID_FOR_USER = 10011;
15
    const ACCOUNT_ID_FOR_BANK = 10025;
16
17
    /**
18
     * In memory max code that keep being incremented if we create several account at once without flushing in DB
19
     */
20
    private ?int $maxCode = null;
21
22
    /**
23
     * Clear all caches
24
     */
25 145
    public function clearCache(): void
26
    {
27 145
        $this->maxCode = null;
28 145
    }
29
30
    /**
31
     * Returns pure SQL to get ID of all objects that are accessible to given user.
32
     *
33
     * @param null|User $user
34
     *
35
     * @return string
36
     */
37 12
    public function getAccessibleSubQuery(?User $user): string
38
    {
39 12
        if (!$user) {
40 1
            return '-1';
41
        }
42
43 11
        if (in_array($user->getRole(), [User::ROLE_RESPONSIBLE, User::ROLE_ADMINISTRATOR], true)) {
44 2
            return $this->getAllIdsQuery();
45
        }
46
47 9
        return $this->getAllIdsForOwnerQuery($user);
48
    }
49
50
    /**
51
     * Unsecured way to get a account from its ID.
52
     *
53
     * This should only be used in tests or controlled environment.
54
     *
55
     * @param int $id
56
     *
57
     * @return Account
58
     */
59 14
    public function getOneById(int $id): Account
60
    {
61
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($id) {
62 14
            return $this->findOneById($id);
63 14
        });
64
65 14
        if (!$account) {
66 1
            throw new \Exception('Account #' . $id . ' not found');
67
        }
68
69 14
        return $account;
70
    }
71
72
    /**
73
     * This will return, and potentially create, an account for the given user
74
     *
75
     * @param User $user
76
     *
77
     * @return Account
78
     */
79 17
    public function getOrCreate(User $user): Account
80
    {
81
        // If an account already exists, because getOrCreate was called once before without flushing in between,
82
        // then can return immediately
83 17
        if ($user->getAccount()) {
84 12
            return $user->getAccount();
85
        }
86
87
        $account = $this->getAclFilter()->runWithoutAcl(function () use ($user) {
88 8
            return $this->findOneByOwner($user);
0 ignored issues
show
Bug introduced by
The method findOneByOwner() does not exist on Application\Repository\AccountRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

88
            return $this->/** @scrutinizer ignore-call */ findOneByOwner($user);
Loading history...
89 8
        });
90
91 8
        if (!$account) {
92 8
            $account = new Account();
93 8
            $this->getEntityManager()->persist($account);
94 8
            $account->setOwner($user);
95 8
            $account->setType(AccountTypeType::LIABILITY);
96 8
            $account->setName($user->getName());
97
98 8
            $parent = $this->getOneById(self::PARENT_ACCOUNT_ID_FOR_USER);
99
100
            // Find the max account code, using the liability parent code as prefix
101 8
            if (!$this->maxCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->maxCode of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
102 7
                $maxQuery = 'SELECT MAX(code) FROM account WHERE code LIKE ' . $this->getEntityManager()->getConnection()->quote($parent->getCode() . '%');
103 7
                $this->maxCode = (int) $this->getEntityManager()->getConnection()->fetchColumn($maxQuery);
104
            }
105
106 8
            $nextCode = ++$this->maxCode;
107 8
            $account->setCode($nextCode);
108
109 8
            $account->setParent($parent);
110
        }
111
112 8
        return $account;
113
    }
114
115
    /**
116
     * Sum balance by account type
117
     *
118
     * @API\Input(type="AccountType")
119
     *
120
     * @param string $accountType
121
     *
122
     * @return Money
123
     */
124 1
    public function totalBalanceByType(string $accountType): Money
125
    {
126 1
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
127 1
            ->select('SUM(balance)')
128 1
            ->from($this->getClassMetadata()->getTableName())
129 1
            ->where('type = :type');
130
131 1
        $qb->setParameter('type', $accountType);
132
133 1
        $result = $qb->execute();
134
135 1
        return Money::CHF($result->fetchColumn());
0 ignored issues
show
Bug introduced by
It seems like $result->fetchColumn() can also be of type false; however, parameter $amount of Money\Money::CHF() does only seem to accept integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

135
        return Money::CHF(/** @scrutinizer ignore-type */ $result->fetchColumn());
Loading history...
136
    }
137
138
    /**
139
     * Update accounts' balance
140
     *
141
     * @param null|Account $account the account to update, or null for all accounts
142
     *
143
     * @throws \Doctrine\DBAL\DBALException
144
     */
145
    public function updateAccountBalance(?Account $account = null): void
146
    {
147
        $connection = $this->getEntityManager()->getConnection();
148
        $sql = 'CALL update_account_balance(?)';
149
150
        if ($account) {
151
            $connection->executeQuery($sql, [$account->getId()]);
152
        } else {
153
            foreach ($this->findAll() as $a) {
154
                $connection->executeQuery($sql, [$a->getId()]);
155
            }
156
        }
157
    }
158
159
    /**
160
     * Return the next available Account code
161
     *
162
     * @return int
163
     */
164
    public function getNextCodeAvailable(): int
165
    {
166
        $qb = _em()->getConnection()->createQueryBuilder()
167
            ->select('IFNULL(MAX(a.code) + 1, 1)')
168
            ->from('account', 'a');
169
170
        return (int) $qb->execute()->fetchColumn();
171
    }
172
}
173