Failed Conditions
Push — master ( 3e915b...b75901 )
by Sylvain
08:58
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 Application\Utility;
11
use Doctrine\DBAL\Exception\InvalidArgumentException;
12
13
class AccountRepository extends AbstractRepository implements LimitedAccessSubQueryInterface
14
{
15
    private const PARENT_ACCOUNT_ID_FOR_USER = 10011;
16
    const ACCOUNT_ID_FOR_BANK = 10025;
17
18
    /**
19
     * @var string[]
20
     */
21
    private $totalBalanceCache = [];
22
23
    /**
24
     * Clear all caches
25
     */
26 1
    public function clearCache(): void
27
    {
28 1
        $this->totalBalanceCache = [];
29 1
    }
30
31
    /**
32
     * Returns pure SQL to get ID of all objects that are accessible to given user.
33
     *
34
     * @param null|User $user
35
     *
36
     * @return string
37
     */
38 11
    public function getAccessibleSubQuery(?User $user): string
39
    {
40 11
        if (!$user) {
41 1
            return '-1';
42
        }
43
44 10
        if (in_array($user->getRole(), [User::ROLE_RESPONSIBLE, User::ROLE_ADMINISTRATOR], true)) {
45 2
            return $this->getAllIdsQuery();
46
        }
47
48 8
        return $this->getAllIdsForOwnerQuery($user);
49
    }
50
51
    /**
52
     * Unsecured way to get a account from its ID.
53
     *
54
     * This should only be used in tests or controlled environment.
55
     *
56
     * @param int $id
57
     *
58
     * @return null|Account
59
     */
60 8
    private function getOneById(int $id): ?Account
61
    {
62 8
        $this->getAclFilter()->setEnabled(false);
63 8
        $account = $this->findOneById($id);
64 8
        $this->getAclFilter()->setEnabled(true);
65
66 8
        return $account;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $account could return the type Application\Model\AbstractModel which includes types incompatible with the type-hinted return Application\Model\Account|null. Consider adding an additional type-check to rule them out.
Loading history...
67
    }
68
69
    /**
70
     * This will return, and potentially create, an account for the given user
71
     *
72
     * @param User $user
73
     *
74
     * @return Account
75
     */
76 16
    public function getOrCreate(User $user): Account
77
    {
78
        // If an account already exists, because getOrCreate was called once before without flushing in between,
79
        // then can return immediately
80 16
        if ($user->getAccount()) {
81 11
            return $user->getAccount();
82
        }
83
84 8
        $this->getAclFilter()->setEnabled(false);
85 8
        $account = $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

85
        /** @scrutinizer ignore-call */ 
86
        $account = $this->findOneByOwner($user);
Loading history...
86 8
        $this->getAclFilter()->setEnabled(true);
87
88 8
        if (!$account) {
89 8
            $account = new Account();
90 8
            $this->getEntityManager()->persist($account);
91 8
            $account->setOwner($user);
92 8
            $account->setType(AccountTypeType::LIABILITY);
93 8
            $account->setName($user->getName());
94
95 8
            $maxCode = $this->getEntityManager()->getConnection()->fetchColumn('SELECT MAX(code) FROM account WHERE parent_id = ' . self::PARENT_ACCOUNT_ID_FOR_USER);
96 8
            $newCode = ++$maxCode;
97 8
            $account->setCode((string) $newCode);
98
99 8
            $parent = $this->getOneById(self::PARENT_ACCOUNT_ID_FOR_USER);
100 8
            if (!$parent) {
101
                throw new \Exception('Cannot find parent account for creation of user account');
102
            }
103 8
            $account->setParent($parent);
104
        }
105
106 8
        return $account;
107
    }
108
109
    /**
110
     * Sum balance by account type
111
     *
112
     * @API\Input(type="AccountType")
113
     *
114
     * @param string $accountType
115
     *
116
     * @return string
117
     */
118 1
    public function totalBalanceByType(string $accountType): string
119
    {
120 1
        $qb = $this->getEntityManager()->getConnection()->createQueryBuilder()
121 1
            ->select('SUM(balance)')
122 1
            ->from($this->getClassMetadata()->getTableName())
123 1
            ->where('type = :type');
124
125 1
        $qb->setParameter('type', $accountType);
126
127 1
        $result = $qb->execute();
128
129 1
        return (string) $result->fetchColumn();
130
    }
131
132
    /**
133
     * Calculate the total balance of all child accounts of a group account
134
     *
135
     * @param Account $parentAccount
136
     *
137
     * @throws \Doctrine\DBAL\DBALException
138
     *
139
     * @return string
140
     */
141 1
    public function totalBalanceByParent(Account $parentAccount): string
142
    {
143 1
        if ($parentAccount->getType() !== AccountTypeType::GROUP) {
144
            throw new InvalidArgumentException(sprintf(
145
                'Cannot compute total balance for Account #%d of type %s',
146
                $parentAccount->getId(),
147
                $parentAccount->getType()
148
            ));
149
        }
150
151 1
        $cacheKey = Utility::getCacheKey(func_get_args());
152 1
        if (array_key_exists($cacheKey, $this->totalBalanceCache)) {
153
            return $this->totalBalanceCache[$cacheKey];
154
        }
155
156 1
        $connection = $this->getEntityManager()->getConnection();
157
158 1
        $sql = 'WITH RECURSIVE child AS
159
          (SELECT id, parent_id, `type`, balance
160
           FROM account WHERE id = ?
161
           UNION
162
           SELECT account.id, account.parent_id, account.type, account.balance
163
           FROM account
164
           JOIN child ON account.parent_id = child.id)
165
        SELECT SUM(balance) FROM child WHERE `type` <> ?';
166
167 1
        $result = $connection->executeQuery($sql, [$parentAccount->getId(), AccountTypeType::GROUP]);
168
169
        return (string) $result->fetchColumn();
170
    }
171
}
172