Search code examples
phploopsforeachfile-get-contentsfile-put-contents

Is there a better way cycle through these PHP codes, maybe with foreach loop?


I use file_get_contents to grab a remote pricing (updated daily), use substr to keep only the portions I want (stripping out the currency symbols and other data from the output and only keeping the numbers) and use file_put_contents to store it into a cache directory which I refer to later.

This is what I have now:-

<?php

$cacheDirectory = $_SERVER['DOCUMENT_ROOT'] . '/cache/';

// Small Plan - US
$cachefile_SM_US = $cacheDirectory . 'SM_US.cache';

if(file_exists($cachefile_SM_US)) {
    if(time() - filemtime($cachefile_SM_US) > 1600) {
        // too old , re-fetch
        $cache_SM_US = file_get_contents('https://remotedomain.com/?get=price&product=10&currency=1');
        $substr_SM_US = substr($cache_SM_US,17,2);
        file_put_contents($cachefile_SM_US, $substr_SM_US);
        } else {
            // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_SM_US = file_get_contents('https://remotedomain.com/?get=price&product=10&currency=1');
    $substr_SM_US = substr($cache_SM_US,17,2);
    file_put_contents($cachefile_SM_US, $substr_SM_US);
}

// Large Plan - US
$cachefile_LG_US = $cacheDirectory . 'LG_US.cache';

if(file_exists($cachefile_LG_US)) {
    if(time() - filemtime($cachefile_LG_US) > 1600) {
        // too old , re-fetch
        $cache_LG_US = file_get_contents('https://remotedomain.com/?get=price&product=20&currency=1');
        $substr_LG_US = substr($cache_LG_US,17,2);
        file_put_contents($cachefile_LG_US, $substr_LG_US);
    } else {
        // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_LG_US = file_get_contents('https://remotedomain.com/?get=price&product=20&currency=1');
    $substr_LG_US = substr($cache_LG_US,17,2);
    file_put_contents($cachefile_LG_US, $substr_LG_US);
}

// Small Plan - EU
$cachefile_SM_EU = $cacheDirectory . 'SM_EU.cache';

if(file_exists($cachefile_SM_EU)) {
    if(time() - filemtime($cachefile_SM_EU) > 1600) {
        // too old , re-fetch
        $cache_SM_EU = file_get_contents('https://remotedomain.com/?get=price&product=10&currency=2');
        $substr_SM_EU = substr($cache_SM_EU,17,2);
        file_put_contents($cachefile_SM_EU, $substr_SM_EU);
        } else {
            // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_SM_EU = file_get_contents('https://remotedomain.com/?get=price&product=10&currency=2');
    $substr_SM_EU = substr($cache_SM_EU,17,2);
    file_put_contents($cachefile_SM_EU, $substr_SM_EU);
}

// Large Plan - EU
$cachefile_LG_EU = $cacheDirectory . 'LG_EU.cache';

if(file_exists($cachefile_LG_EU)) {
    if(time() - filemtime($cachefile_LG_EU) > 1600) {
        // too old , re-fetch
        $cache_LG_EU = file_get_contents('https://remotedomain.com/?get=price&product=20&currency=2');
        $substr_LG_EU = substr($cache_LG_EU,17,2);
        file_put_contents($cachefile_LG_EU, $substr_LG_EU);
    } else {
        // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_LG_EU = file_get_contents('https://remotedomain.com/?get=price&product=20&currency=2');
    $substr_LG_EU = substr($cache_LG_EU,17,2);
    file_put_contents($cachefile_LG_EU, $substr_LG_EU);
}

?>

This manual way works when there are only two products (10 and 20) and two currencies (1 and 2) as I only need to do it 4 times to get all the pricing I need.

However, I am going to significantly expand the number of products to at least 12 products and 9 currencies so it is not realistic to do them manually.

I believe this can be done more efficiently with PHP foreach loop but I tried a few days and didn't manage to get it to work, maybe because of my weaker understanding of the concept.

I managed to split it up into this:-

<?php

$cacheDirectory = $_SERVER['DOCUMENT_ROOT'] . '/cache/';

$url = 'https://remotedomain.com/?get=price';
$productA = 10;
$productB = 20;
$USD = 1;
$EUR = 2;

// Small Plan - US
$cachefile_SM_US = $cacheDirectory . 'SM_US.cache';

if(file_exists($cachefile_SM_US)) {
    if(time() - filemtime($cachefile_SM_US) > 1600) {
        // too old , re-fetch
        $cache_SM_US = file_get_contents($url . '&product=' . $productA . '&currency=' . $USD);
        $substr_SM_US = substr($cache_SM_US,17,2);
        file_put_contents($cachefile_SM_US, $substr_SM_US);
        } else {
            // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_SM_US = file_get_contents($url . '&product=' . $productA . '&currency=' . $USD);
    $substr_SM_US = substr($cache_SM_US,17,2);
    file_put_contents($cachefile_SM_US, $substr_SM_US);
}

// Large Plan - US
$cachefile_LG_US = $cacheDirectory . 'LG_US.cache';

if(file_exists($cachefile_LG_US)) {
    if(time() - filemtime($cachefile_LG_US) > 1600) {
        // too old , re-fetch
        $cache_LG_US = file_get_contents($url . '&product=' . $productB . '&currency=' . $USD);
        $substr_LG_US = substr($cache_LG_US,17,2);
        file_put_contents($cachefile_LG_US, $substr_LG_US);
    } else {
        // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_LG_US = file_get_contents($url . '&product=' . $productB . '&currency=' . $USD);
    $substr_LG_US = substr($cache_LG_US,17,2);
    file_put_contents($cachefile_LG_US, $substr_LG_US);
}

// Small Plan - EU
$cachefile_SM_EU = $cacheDirectory . 'SM_EU.cache';

if(file_exists($cachefile_SM_EU)) {
    if(time() - filemtime($cachefile_SM_EU) > 1600) {
        // too old , re-fetch
        $cache_SM_EU = file_get_contents($url . '&product=' . $productA . '&currency=' . $EUR);
        $substr_SM_EU = substr($cache_SM_EU,17,2);
        file_put_contents($cachefile_SM_EU, $substr_SM_EU);
        } else {
            // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_SM_EU = file_get_contents($url . '&product=' . $productA . '&currency=' . $EUR);
    $substr_SM_EU = substr($cache_SM_EU,17,2);
    file_put_contents($cachefile_SM_EU, $substr_SM_EU);
}

// Large Plan - EU
$cachefile_LG_EU = $cacheDirectory . 'LG_EU.cache';

if(file_exists($cachefile_LG_EU)) {
    if(time() - filemtime($cachefile_LG_EU) > 1600) {
        // too old , re-fetch
        $cache_LG_EU = file_get_contents($url . '&product=' . $productB . '&currency=' . $EUR);
        $substr_LG_EU = substr($cache_LG_EU,17,2);
        file_put_contents($cachefile_LG_EU, $substr_LG_EU);
    } else {
        // cache is still fresh
    }
} else {
    // no cache, create one
    $cache_LG_EU = file_get_contents($url . '&product=' . $productB . '&currency=' . $EUR);
    $substr_LG_EU = substr($cache_LG_EU,17,2);
    file_put_contents($cachefile_LG_EU, $substr_LG_EU);
}

?>

The challenge I have now is how to turn this into a foreach loop which will cycle through each product and each currencies.

Appreciate pointers in the right direction.

Thank you!


Solution

  • Absolutely. Take a look at this example :)

    <?php declare(strict_types=1);
    
    interface CacheNormalizer
    {
        public function normalize(string $text): string;
    }
    
    interface PlanDomainToCache
    {
        public function buildUrl(Plan $plan): string;
    }
    
    final class CachedRemoteSiteManager
    {
        /** @var int Time To Live Cache */
        private $timeToLive;
    
        /** @var CacheNormalizer */
        private $cacheNormalizer;
    
        /** @var PlanDomainToCache */
        private $planDomainToCache;
    
        public function __construct(
            int $timeToLive,
            CacheNormalizer $cacheNormalizer,
            PlanDomainToCache $planDomainToCache
        ) {
            $this->timeToLive = $timeToLive;
            $this->cacheNormalizer = $cacheNormalizer;
            $this->planDomainToCache = $planDomainToCache;
        }
    
        public function updateIfNecessary(Plan $plan): void
        {
            if ($this->shouldCreateOrUpdateCache($plan)) {
                $this->createOrUpdateCache($plan);
            }
        }
    
        private function shouldCreateOrUpdateCache(Plan $plan): bool
        {
            return !file_exists($plan->cacheDirectory())
                || time() - filemtime($plan->cacheDirectory()) > $this->timeToLive;
        }
    
        private function createOrUpdateCache(Plan $plan): void
        {
            $urlToCache = $this->planDomainToCache->buildUrl($plan);
            $textToCache = file_get_contents($urlToCache);
    
            file_put_contents(
                $plan->cacheDirectory(),
                $this->cacheNormalizer->normalize($textToCache)
            );
        }
    }
    
    final class Plan
    {
        /** @var string */
        private $cacheDirectory;
    
        /** @var int */
        private $product;
    
        /** @var int */
        private $currency;
    
        public function __construct(string $cacheDir, int $product, int $currency)
        {
            $this->cacheDirectory = $cacheDir;
            $this->product = $product;
            $this->currency = $currency;
        }
    
        public function cacheDirectory(): string
        {
            return $this->cacheDirectory;
        }
    
        public function product(): int
        {
            return $this->product;
        }
    
        public function currency(): int
        {
            return $this->currency;
        }
    }
    
    // Usage example:
    
    $cacheDirectory = $_SERVER['DOCUMENT_ROOT'] . '/cache/';
    $productA = 10;
    $productB = 20;
    $USD = 1;
    $EUR = 2;
    
    /** @var Plan[] */
    $plansToCache = [
        new Plan($cacheDirectory . 'SM_US.cache', $productA, $USD),
        new Plan($cacheDirectory . 'LG_US.cache', $productB, $USD),
        new Plan($cacheDirectory . 'SM_EU.cache', $productA, $EUR),
        new Plan($cacheDirectory . 'LG_EU.cache', $productB, $EUR),
    ];
    
    $cacheManager = new CachedRemoteSiteManager(
        $cacheTtl = 1600,
        new class implements CacheNormalizer {
            public function normalize(string $text): string
            {
                return substr($text, 17, 2);
            }
        },
        new class implements PlanDomainToCache {
            public function buildUrl(Plan $plan): string
            {
                return sprintf(
                    'https://remotedomain.com/?get=price&product=%d&currency=%d',
                    $plan->product(),
                    $plan->currency()
                );
            }
        }
    );
    
    foreach ($plansToCache as $plan) {
        $cacheManager->updateIfNecessary($plan);
    }
    

    As you can see at the bottom, in the "usage example", I extracted all details (almost all of them) so we can easily define:

    • how we want to normalize the cached data (using the CacheNormalizer)
    • how we want to build the URL that we want to cache (using PlanDomainToCache).

    UPDATED:

    If you want to see how could you extract/decouple every detail from the ending code, inverting the dependencies upwards even for the "Persistence" layer: https://gist.github.com/Chemaclass/01d3f42685ff69f6897192202a32014d