44
55use Civi \WorkflowMessage \WorkflowMessage ;
66use CRM_Financeextras_CustomGroup_ContributionOwnerOrganisation as ContributionOwnerOrganisation ;
7+ use Civi \Financeextras \Common \GCManager ;
78
89/**
910 * Provides separate invoicing template and tokens for each
@@ -16,36 +17,72 @@ class InvoiceTemplate {
1617 private $ contributionId ;
1718
1819 private $ contributionOwnerCompany ;
20+
21+ private static $ processedInvoices = 0 ;
22+ private static $ contributionCache = [];
23+ private static $ contributionCacheOrder = [];
24+ private static $ ownerCompanyCache = [];
25+ private static $ ownerCompanyCacheOrder = [];
26+ private static $ locationCache = [];
27+ private static $ locationCacheOrder = [];
28+ private static $ maxCacheSize = 100 ;
1929
2030 public function __construct (&$ templateParams , $ context ) {
2131 $ this ->templateParams = &$ templateParams ;
2232 $ this ->contributionId = $ templateParams ['tplParams ' ]['id ' ];
2333 }
2434
2535 public function handle () {
26- $ this ->addTaxConversionTable ();
36+ self ::$ processedInvoices ++;
37+
38+ try {
39+ $ this ->addTaxConversionTable ();
2740
28- $ this ->contributionOwnerCompany = ContributionOwnerOrganisation::getOwnerOrganisationCompany ($ this ->contributionId );
29- if (empty ($ this ->contributionOwnerCompany )) {
30- return ;
31- }
41+ // Get owner company from cache or fetch
42+ $ this ->contributionOwnerCompany = $ this ->getOwnerCompanyFromCache ($ this ->contributionId );
43+ if (!$ this ->contributionOwnerCompany ) {
44+ $ this ->contributionOwnerCompany = ContributionOwnerOrganisation::getOwnerOrganisationCompany ($ this ->contributionId );
45+ // Cache using LRU
46+ $ this ->addToLRUCache (self ::$ ownerCompanyCache , self ::$ ownerCompanyCacheOrder , $ this ->contributionId , $ this ->contributionOwnerCompany );
47+ }
48+
49+ if (empty ($ this ->contributionOwnerCompany )) {
50+ return ;
51+ }
3252
33- $ this ->useContributionOwnerOrganisationInvoiceTemplate ();
34- $ this ->replaceDomainTokensWithOwnerOrganisationTokens ();
53+ $ this ->useContributionOwnerOrganisationInvoiceTemplate ();
54+ $ this ->replaceDomainTokensWithOwnerOrganisationTokens ();
55+
56+ // Adaptive memory management: Batch-complete trigger after each invoice
57+ // Uses conservative approach with memory-threshold backup
58+ GCManager::maybeCollectGarbage ('invoice_processing ' );
59+ } catch (Exception $ e ) {
60+ // Log error and continue processing other invoices
61+ \Civi::log ()->error ('InvoiceTemplate processing failed for contribution ' . $ this ->contributionId . ': ' . $ e ->getMessage ());
62+ throw $ e ;
63+ }
3564 }
3665
3766 private function addTaxConversionTable () {
3867 $ showTaxConversionTable = TRUE ;
39- $ contribution = \Civi \Api4 \Contribution::get (FALSE )
40- ->addSelect (
41- 'financeextras_currency_exchange_rates.rate_1_unit_tax_currency ' ,
42- 'financeextras_currency_exchange_rates.rate_1_unit_contribution_currency ' ,
43- 'financeextras_currency_exchange_rates.sales_tax_currency ' ,
44- 'financeextras_currency_exchange_rates.vat_text '
45- )->setLimit (1 )
46- ->addWhere ('id ' , '= ' , $ this ->contributionId )
47- ->execute ()
48- ->first ();
68+
69+ // Check LRU cache first
70+ $ contribution = $ this ->getContributionFromCache ($ this ->contributionId );
71+ if (!$ contribution ) {
72+ $ contribution = \Civi \Api4 \Contribution::get (FALSE )
73+ ->addSelect (
74+ 'financeextras_currency_exchange_rates.rate_1_unit_tax_currency ' ,
75+ 'financeextras_currency_exchange_rates.rate_1_unit_contribution_currency ' ,
76+ 'financeextras_currency_exchange_rates.sales_tax_currency ' ,
77+ 'financeextras_currency_exchange_rates.vat_text '
78+ )->setLimit (1 )
79+ ->addWhere ('id ' , '= ' , $ this ->contributionId )
80+ ->execute ()
81+ ->first ();
82+
83+ // Cache the result using LRU
84+ $ this ->addToLRUCache (self ::$ contributionCache , self ::$ contributionCacheOrder , $ this ->contributionId , $ contribution );
85+ }
4986 if (empty ($ contribution ['financeextras_currency_exchange_rates.rate_1_unit_tax_currency ' ])) {
5087 $ showTaxConversionTable = FALSE ;
5188 }
@@ -137,7 +174,14 @@ private function replaceDomainTokensWithOwnerOrganisationTokens() {
137174 */
138175 private function getOwnerOrganisationLocation () {
139176 $ ownerOrganisationId = $ this ->contributionOwnerCompany ['contact_id ' ];
140- $ locationDefaults = \CRM_Core_BAO_Location::getValues (['contact_id ' => $ ownerOrganisationId ]);
177+
178+ // Check LRU cache first
179+ $ locationDefaults = $ this ->getLocationFromCache ($ ownerOrganisationId );
180+ if (!$ locationDefaults ) {
181+ $ locationDefaults = \CRM_Core_BAO_Location::getValues (['contact_id ' => $ ownerOrganisationId ]);
182+ // Cache using LRU
183+ $ this ->addToLRUCache (self ::$ locationCache , self ::$ locationCacheOrder , $ ownerOrganisationId , $ locationDefaults );
184+ }
141185
142186 if (!empty ($ locationDefaults ['address ' ][1 ]['state_province_id ' ])) {
143187 $ locationDefaults ['address ' ][1 ]['state_province_abbreviation ' ] = \CRM_Core_PseudoConstant::stateProvinceAbbreviation ($ locationDefaults ['address ' ][1 ]['state_province_id ' ]);
@@ -155,5 +199,72 @@ private function getOwnerOrganisationLocation() {
155199
156200 return $ locationDefaults ;
157201 }
202+
203+ /**
204+ * Gets contribution data from LRU cache.
205+ */
206+ private function getContributionFromCache ($ contributionId ) {
207+ if (isset (self ::$ contributionCache [$ contributionId ])) {
208+ $ this ->updateLRUOrder (self ::$ contributionCacheOrder , $ contributionId );
209+ return self ::$ contributionCache [$ contributionId ];
210+ }
211+ return FALSE ;
212+ }
213+
214+ /**
215+ * Gets owner company data from LRU cache.
216+ */
217+ private function getOwnerCompanyFromCache ($ contributionId ) {
218+ if (isset (self ::$ ownerCompanyCache [$ contributionId ])) {
219+ $ this ->updateLRUOrder (self ::$ ownerCompanyCacheOrder , $ contributionId );
220+ return self ::$ ownerCompanyCache [$ contributionId ];
221+ }
222+ return FALSE ;
223+ }
224+
225+ /**
226+ * Gets location data from LRU cache.
227+ */
228+ private function getLocationFromCache ($ contactId ) {
229+ if (isset (self ::$ locationCache [$ contactId ])) {
230+ $ this ->updateLRUOrder (self ::$ locationCacheOrder , $ contactId );
231+ return self ::$ locationCache [$ contactId ];
232+ }
233+ return FALSE ;
234+ }
235+
236+ /**
237+ * Updates LRU order by moving item to end (most recently used).
238+ */
239+ private function updateLRUOrder (&$ orderArray , $ key ) {
240+ $ index = array_search ($ key , $ orderArray );
241+ if ($ index !== FALSE ) {
242+ unset($ orderArray [$ index ]);
243+ $ orderArray = array_values ($ orderArray ); // Re-index array
244+ }
245+ $ orderArray [] = $ key ;
246+ }
247+
248+ /**
249+ * Adds item to LRU cache, evicting least recently used if at capacity.
250+ */
251+ private function addToLRUCache (&$ cache , &$ orderArray , $ key , $ value ) {
252+ // If already exists, update value and move to end
253+ if (isset ($ cache [$ key ])) {
254+ $ cache [$ key ] = $ value ;
255+ $ this ->updateLRUOrder ($ orderArray , $ key );
256+ return ;
257+ }
258+
259+ // If at capacity, remove least recently used item
260+ if (count ($ cache ) >= self ::$ maxCacheSize ) {
261+ $ lruKey = array_shift ($ orderArray );
262+ unset($ cache [$ lruKey ]);
263+ }
264+
265+ // Add new item
266+ $ cache [$ key ] = $ value ;
267+ $ orderArray [] = $ key ;
268+ }
158269
159270}
0 commit comments