Mi API BCV uses the Strategy pattern to handle different scraping approaches for each bank. This design pattern allows the system to be easily extended with new data sources without modifying existing code.
Location: app/Strategies/Url/UrlBdv.phpThis strategy is more complex because BDV uses a JSON API instead of HTML scraping:
namespace App\Strategies\Url;use App\Interfaces\UrlStrategy;use Illuminate\Support\Facades\Http;use Illuminate\Support\Facades\Log;use Exception;class UrlBdv implements UrlStrategy{ protected string $url = 'https://www.bancodevenezuela.com/files/tasas/tasas2.json'; public function getValue(): float { try { $response = Http::timeout(10) ->withHeaders([ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36', 'Accept' => 'application/json', ]) ->get($this->url); if ($response->failed()) { throw new Exception("Error de conexión con BDV: " . $response->status()); } $data = $response->json(); $rawRate = data_get($data, 'menudeo.compra.dolares'); if (!$rawRate) { throw new Exception("Estructura de JSON no reconocida o tasa faltante."); } return $this->formatRate($rawRate); } catch (Exception $e) { Log::error("Fallo obteniendo tasa BDV: " . $e->getMessage()); return "0.00"; } } private function formatRate(string $rate): float { $cleanRate = str_replace(',', '.', $rate); return (float) preg_replace('/[^0-9.]/', '', $cleanRate); }}
Key Differences:
BDV doesn’t receive ScraperService in constructor (doesn’t need it)
Makes HTTP request directly to BDV’s JSON API
Uses data_get($data, 'menudeo.compra.dolares') to extract the rate
Returns string “0.00” on error (note: this is a bug in the source - should return float)
This demonstrates the flexibility of the Strategy pattern—different strategies can use completely different approaches while maintaining the same interface.
Location: app/Services/UrlProviderService.phpThe UrlProviderService acts as the context in the Strategy pattern. It manages and provides access to concrete strategies.
namespace App\Services;use App\Interfaces\UrlStrategy;use App\Strategies\Url\UrlBanplus;use App\Strategies\Url\UrlBcv;use App\Strategies\Url\UrlBdv;use App\Strategies\Url\UrlBnc;class UrlProviderService{ public function getStrategy(string $identifier): UrlStrategy { return match ($identifier) { 'banplus' => new UrlBanplus(new ScraperService()), 'bnc' => new UrlBnc(new ScraperService()), 'bcv' => new UrlBcv(new ScraperService()), 'bdv' => new UrlBdv(), default => new UrlBdv(), }; }}
Responsibilities:
Instantiate strategies on-demand using a match expression
Create a new ScraperService for each HTML-scraping strategy (banplus, bnc, bcv)
BDV is instantiated without ScraperService since it uses JSON API directly
// app/Strategies/Url/UrlNuevoBanco.phpnamespace App\Strategies\Url;use App\Interfaces\UrlStrategy;class UrlNuevoBanco implements UrlStrategy{ protected $scraper; public function __construct($scraper) { $this->scraper = $scraper; } public function getValue(): float { $url = 'https://www.nuevobanco.com.ve/'; return $this->scraper->scrapeData($url, 'nuevobanco'); }}
2
Register in UrlProviderService
Update the match expression in getStrategy() method:
public function getStrategy(string $identifier): UrlStrategy{ return match ($identifier) { 'banplus' => new UrlBanplus(new ScraperService()), 'bnc' => new UrlBnc(new ScraperService()), 'bcv' => new UrlBcv(new ScraperService()), 'bdv' => new UrlBdv(), 'nuevobanco' => new UrlNuevoBanco(new ScraperService()), // Add this default => new UrlBdv(), };}
The Strategy pattern is the right choice here because bank-specific logic is complex enough to warrant separate classes, but not so complex as to require heavier patterns.