OFX   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 108
c 1
b 0
f 0
dl 0
loc 207
rs 10
wmc 21

9 Methods

Rating   Name   Duplication   Size   Complexity  
A parseStatus() 0 6 1
A parseAccountInfo() 0 14 4
A parseInstitute() 0 5 1
A parseSignOn() 0 7 1
A parse() 0 24 6
A parseCreditAccount() 0 23 2
A parseBankAccount() 0 18 1
A parseDate() 0 26 2
A parseStatement() 0 32 3
1
<?php
2
3
namespace Endeken\OFX;
4
5
use DateTime;
6
use DateTimeZone;
7
use Exception;
8
use SimpleXMLElement;
9
10
/**
11
 * Class OFX
12
 *
13
 * This class provides functions to parse OFX data and convert it into an associative array or appropriate objects.
14
 */
15
class OFX
16
{
17
    /**
18
     * Parse OFX data and return an associative array with the parsed information.
19
     *
20
     * @param string $ofxData The OFX data as a string.
21
     * @return OFXData|null An associative array with the parsed information or false on failure.
22
     * @throws Exception
23
     */
24
    public static function parse(string $ofxData): null|OFXData
25
    {
26
27
        // Check if SimpleXML object was created successfully
28
29
        $xml = OFXUtils::normalizeOfx($ofxData);
30
        if ($xml === false) {
31
            return null;
32
        }
33
34
        $signOn = self::parseSignOn($xml->SIGNONMSGSRSV1->SONRS);
35
        $accountInfo = self::parseAccountInfo($xml->SIGNUPMSGSRSV1->ACCTINFOTRNRS);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $accountInfo is correct as self::parseAccountInfo($...SGSRSV1->ACCTINFOTRNRS) targeting Endeken\OFX\OFX::parseAccountInfo() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
36
        $bankAccounts = [];
37
38
        if (isset($xml->BANKMSGSRSV1)) {
39
            foreach ($xml->BANKMSGSRSV1->STMTTRNRS as $accountStatement) {
40
                foreach ($accountStatement->STMTRS as $statementResponse) {
41
                    $bankAccounts[] = self::parseBankAccount($accountStatement->TRNUID, $statementResponse);
0 ignored issues
show
Bug introduced by
It seems like $statementResponse can also be of type null; however, parameter $xml of Endeken\OFX\OFX::parseBankAccount() does only seem to accept SimpleXMLElement, 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

41
                    $bankAccounts[] = self::parseBankAccount($accountStatement->TRNUID, /** @scrutinizer ignore-type */ $statementResponse);
Loading history...
42
                }
43
            }
44
        } elseif (isset($xml->CREDITCARDMSGSRSV1)) {
45
            $bankAccounts[] = self::parseCreditAccount($xml->TRNUID, $xml);
46
        }
47
        return new OFXData($signOn, $accountInfo, $bankAccounts);
48
    }
49
50
    /**
51
     * @param SimpleXMLElement $xml
52
     * @return SignOn
53
     * @throws Exception
54
     */
55
    protected static function parseSignOn(SimpleXMLElement $xml): SignOn
56
    {
57
        $status = self::parseStatus($xml->STATUS);
58
        $dateTime = self::parseDate($xml->DTSERVER);
59
        $language = $xml->LANGUAGE;
60
        $institute = self::parseInstitute($xml->FI);
61
        return new SignOn($status, $dateTime, $language, $institute);
62
    }
63
64
    protected static function parseInstitute(SimpleXMLElement $xml): Institute
65
    {
66
        $name = (string) $xml->ORG;
67
        $id = (string) $xml->FID;
68
        return new Institute($id, $name);
69
    }
70
71
    /**
72
     * @param SimpleXMLElement $xml
73
     * @return Status
74
     */
75
    protected static function parseStatus(SimpleXMLElement $xml): Status
76
    {
77
        $code = (string) $xml->STATUS->CODE;
78
        $severity = (string) $xml->STATUS->SEVERITY;
79
        $message = (string) $xml->STATUS->MESSAGE;
80
        return new Status($code, $severity, $message);
81
    }
82
83
    /**
84
     * Parse a date string and return a formatted date.
85
     *
86
     * @param string $dateString The date string to parse.
87
     * @return DateTime The formatted date.
88
     * @throws Exception
89
     */
90
    protected static function parseDate(string $dateString): DateTime
91
    {
92
        $dateString = explode('.', $dateString)[0];
93
        // Extract the numeric part of the offset (e.g., -5 from [-5:EST])
94
        preg_match('/([-+]\d+):(\w+)/', $dateString, $matches);
95
96
        if (count($matches) === 3) {
97
            $offset = $matches[1];
98
            $timezoneAbbreviation = $matches[2];
99
100
            // Remove the offset with brackets and timezone abbreviation from the date string
101
            $dateStringWithoutOffset = preg_replace('/[-+]\d+:\w+/', '', $dateString);
102
103
            // Remove brackets and timezone abbreviation
104
            $dateStringWithoutOffset = str_replace(['[', ']', ':' . $timezoneAbbreviation], '', $dateStringWithoutOffset);
105
106
            // Create a DateTime object with the appropriate timezone offset
107
            $dateTime = new DateTime($dateStringWithoutOffset, new DateTimeZone("GMT$offset"));
108
            $dateTime->setTimezone(new DateTimeZone($timezoneAbbreviation));
109
110
        } else {
111
            // Handle cases where the date format doesn't match expectations
112
            // You might want to log an error or throw an exception depending on your needs
113
            $dateTime = new DateTime($dateString);
114
        }
115
        return $dateTime;
116
    }
117
118
    /**
119
     * @throws Exception
120
     */
121
    private static function parseBankAccount(string $uuid, SimpleXMLElement $xml): BankAccount
122
    {
123
        $accountNumber = $xml->BANKACCTFROM->ACCTID;
124
        $accountType = $xml->BANKACCTFROM->ACCTTYPE;
125
        $agencyNumber = $xml->BANKACCTFROM->BRANCHID;
126
        $routingNumber = $xml->BANKACCTFROM->BANKID;
127
        $balance = $xml->LEDGERBAL->BALAMT;
128
        $balanceDate = self::parseDate($xml->LEDGERBAL->DTASOF);
129
        $statement = self::parseStatement($xml);
130
        return new BankAccount(
131
            $accountNumber,
132
            $accountType,
133
            $agencyNumber,
134
            $routingNumber,
135
            $balance,
136
            $balanceDate,
137
            $uuid,
138
            $statement,
139
        );
140
    }
141
142
    /**
143
     * @throws Exception
144
     */
145
    private static function parseCreditAccount(string $uuid, SimpleXMLElement $xml): BankAccount
146
    {
147
        $nodeName = 'CCACCTFROM';
148
        if (!isset($xml->CCSTMTRS->$nodeName)) {
149
            $nodeName = 'BANKACCTFROM';
150
        }
151
152
        $accountNumber = $xml->CCSTMTRS->$nodeName->ACCTID;
153
        $accountType = $xml->CCSTMTRS->$nodeName->ACCTTYPE;
154
        $agencyNumber = $xml->CCSTMTRS->$nodeName->BRANCHID;
155
        $routingNumber = $xml->CCSTMTRS->$nodeName->BANKID;
156
        $balance = $xml->CCSTMTRS->LEDGERBAL->BALAMT;
157
        $balanceDate = self::parseDate($xml->CCSTMTRS->LEDGERBAL->DTASOF);
158
        $statement = self::parseStatement($xml);
159
        return new BankAccount(
160
            $accountNumber,
161
            $accountType,
162
            $agencyNumber,
163
            $routingNumber,
164
            $balance,
165
            $balanceDate,
166
            $uuid,
167
            $statement,
168
        );
169
    }
170
171
    /**
172
     * @throws Exception
173
     */
174
    private static function parseStatement(SimpleXMLElement $xml): Statement
175
    {
176
        $currency = $xml->CURDEF;
177
        $startDate = self::parseDate($xml->BANKTRANLIST->DTSTART);
178
        $endDate = self::parseDate($xml->BANKTRANLIST->DTEND);
179
        $transactions = [];
180
        foreach ($xml->BANKTRANLIST->STMTTRN as $transactionXml) {
181
            $type = (string) $transactionXml->TRNTYPE;
182
            $date = self::parseDate($transactionXml->DTPOSTED);
183
            $userInitiatedDate = null;
184
            if (!empty((string) $transactionXml->DTUSER)) {
185
                $userInitiatedDate = self::parseDate($transactionXml->DTUSER);
186
            }
187
            $amount = (float) $transactionXml->TRNAMT;
188
            $uniqueId = (string) $transactionXml->FITID;
189
            $name = (string) $transactionXml->NAME;
190
            $memo = (string) $transactionXml->MEMO;
191
            $sic = $transactionXml->SIC;
192
            $checkNumber = $transactionXml->CHECKNUM;
193
            $transactions[] = new Transaction(
194
                $type,
195
                $amount,
196
                $date,
197
                $userInitiatedDate,
198
                $uniqueId,
199
                $name,
200
                $memo,
201
                $sic,
202
                $checkNumber,
203
            );
204
        }
205
        return new Statement($currency, $transactions, $startDate, $endDate);
206
    }
207
208
    private static function parseAccountInfo(SimpleXMLElement $xml = null): array|null
209
    {
210
        if ($xml === null || !isset($xml->ACCTINFO)) {
211
            return null;
212
        }
213
        $accounts = [];
214
        foreach ($xml->ACCTINFO as $account) {
215
            $accounts[] = new AccountInfo(
216
                (string)$account->DESC,
217
                (string)$account->ACCTID
218
            );
219
        }
220
221
        return $accounts;
222
    }
223
}
224