Download the PHP package peterlupu/curs-bnr without Composer

On this page you can find all versions of the php package peterlupu/curs-bnr. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package curs-bnr

๐Ÿ’ฑ curs-bnr

CI Packagist Version PHP Version Code Style Downloads

Official BNR exchange rates for PHP โ€” with the fiscal semantics done right.

curs-bnr fetches the official exchange rates of the National Bank of Romania (cursul valutar BNR) and answers the question every Romanian invoicing, accounting, or pricing app eventually asks: "which rate applies on this date?" โ€” exactly as the Fiscal Code defines it.

๐Ÿ“ฆ Installation

๐Ÿš€ Quick start

That's it. No API keys, no configuration โ€” BNR's feeds are public.

๐Ÿงพ "Which rate applies today?" โ€” the D vs D+1 rule

This is the part every BNR consumer gets subtly wrong, so it deserves its own section.

BNR communicates rates each banking day around 13:00 Romanian time. Per Fiscal Code art. 290, the rate communicated on day D is valid on day D+1 โ€” and it stays valid through any weekend or legal holiday that follows, until the next communication.

A real example, January 2024:

Date What happened Valid rate (EUR)
Thu 25 BNR communicates 4.9765 the rate from Wed 24โ€ฆ
Fri 26 BNR communicates 4.9763 4.9765 (Thursday's)
Sat 27 nothing communicated 4.9763 (Friday's)
Sun 28 nothing communicated 4.9763 (Friday's)
Mon 29 BNR communicates again 4.9763 (still Friday's)

Pick your method by the question you're asking:

You're askingโ€ฆ Use Weekend/holiday behavior
Which rate is legally valid on this date? (invoices, VAT, contracts) validOn($currency, $date) walks back to the last communication before the date โœ…
What did BNR communicate on this exact day? communicatedOn($currency, $date) throws NoRateCommunicatedException ๐Ÿšซ
What's the most recent rate right now? latest($currency) n/a โ€” reads the daily feed

๐Ÿ’ก If you were previously doing $date->subDay() and then looping backwards until a rate showed up โ€” validOn() is that, done correctly, including holiday runs and year boundaries.

๐Ÿ’ถ Rates, multipliers, and the Rate object

Every lookup returns a CursBnr\Rate:

Notes worth knowing:

๐Ÿ”€ Currency pairs and cross rates

pair($from, $to, $validOn = null) returns the factor that converts an amount of $from into $to:

๐Ÿ“š A whole day of rates in one call

Updating a rates table nightly? Don't make 30 requests:

The same works for any date โ€” one lookup, every currency:

๐Ÿ•ฐ๏ธ Dates, timezones, and other sharp edges

๐Ÿšจ Error handling

Every runtime failure extends one base class (programmer errors โ€” a negative retry count, a malformed date string โ€” throw plain SPL \InvalidArgumentException/\LogicException, as usual):

Or catch precisely:

Exception When Useful properties
TransportException HTTP failed (after retries) $url, $attempts
ParseException feed returned something that isn't a valid BNR document (incl. non-numeric rate values) $url
UnknownCurrencyException currency not quoted by BNR (incl. RON in single-currency calls) $currency
NoRateCommunicatedException communicatedOn() hit a weekend/holiday โ€” or today, before ~13:00 $date
RateNotYetAvailableException dated lookup for a day whose communication hasn't happened yet $date, $today
WalkBackExhaustedException validOn() found nothing within the bound $date, $daysTried
UnsupportedDateException date (or walk-back) before 2005 $date

All exception properties are readonly โ€” safe to log, impossible to mangle.

๐Ÿ”ง Advanced configuration

Everything is constructor-injected and optional:

Retries ๐Ÿ”

Transient HTTP failures are retried automatically (default: 3 attempts, 200ms/400ms backoff). Tune it, give it a closure, or turn it off:

Only transport errors are retried โ€” a malformed document will never be re-fetched in a loop.

The clock ๐Ÿ•

The future-date guard needs to know what "today" is (Europe/Bucharest by default). For deterministic tests of code that uses the real client, inject one:

(For application tests you'll usually reach for FakeCursBnr instead โ€” see below.)

Bring your own HTTP client ๐ŸŒ

The default client is built on PHP streams and needs nothing installed. If you'd rather route through your existing stack, implement the one-method interface:

โ€ฆor adapt any PSR-18 client (Guzzle shown; requires psr/http-client + a PSR-17 factory, which you likely already have):

Custom feed URLs ๐Ÿงญ

Point at a mirror, a proxy, or a test server:

๐Ÿ—„๏ธ Caching (there isn't any โ€” on purpose)

The package performs no caching: a rate for a past date never changes, but your app knows best how long to remember it and where. The only smartness inside is per-instance memoization of already-parsed yearly documents, so one walk-back over a weekend doesn't re-download the same file three times.

Wrapping it is a one-liner with whatever cache you already have (PSR-16 shown):

In a framework, register CursBnr\CursBnr as a singleton in your DI container and inject it โ€” there's no service provider or bundle to install, and none is needed.

๐Ÿงฌ Data source & reliability notes

๐Ÿงช Testing your app

The package ships everything you need to test code that depends on exchange rates โ€” no HTTP, no XML fixtures, no mocking library.

Step 1: depend on the interface

Type-hint CursBnr\ExchangeRates (not the concrete class) and bind the real client in your DI container:

Step 2: use the built-in fake in tests

CursBnr\Testing\FakeCursBnr is an in-memory ExchangeRates implementation. For most tests, constant mode is all you need โ€” one timeless answer for any date:

Step 3: go date-aware when the calendar matters

Register specific communications and the fake behaves exactly like production โ€” strictly-before validOn(), weekend/holiday walk-back, the works:

Step 4: test your error handling too

The fake throws the same typed exceptions as the real client, so your failure paths get real coverage instead of hand-waved mocks:

๐Ÿ”ฌ The fake cannot drift from the real client: both run the exact same internal semantics code (walk-back, future-date guard, 2005 floor) โ€” and a parity test runs a full scenario through both anyway, asserting identical values and identical exceptions.

Testing the wiring itself

If you specifically want to exercise the real parser/HTTP pipeline (rarely needed), the lower-level seam is still there: implement the one-method HttpClientInterface returning canned XML and point FeedUrls at test URLs.

๐Ÿงฉ Wiring it into your framework

For plain unit tests you don't need a container at all โ€” constructor-inject ExchangeRates and pass the fake directly:

The container binding matters for production wiring and for feature/integration tests, where your framework constructs the objects. Recipes:

Laravel

Symfony

Any PSR-11 container (PHP-DI, League, ...)

Alias CursBnr\ExchangeRates to a shared CursBnr\CursBnr instance; in tests, register FakeCursBnr under the same id before booting the code under test.

โ˜๏ธ Whatever the framework: depend on ExchangeRates, never on CursBnr directly, and the production/test split stays a one-line binding.

๐Ÿ“ Versioning & backward compatibility

The package follows semver. The public API is everything under CursBnr\ except classes and methods marked @internal โ€” those may change in any release. While on 0.x, breaking changes may land in minor versions and are always called out in the CHANGELOG; from 1.0 on, they only happen in major versions.

Security issues: see SECURITY.md โ€” please report privately.

๐Ÿค Development & AI transparency

This package is developed with AI assistance (Claude Code) โ€” disclosure level ai-generated per the ai-disclosure convention; details in AI_DISCLOSURE.md. Every release is human-reviewed and gated by the full test suite (run against real BNR XML documents), PHPStan at level max, code style checks, and CI across all supported PHP versions. Issues and PRs are read by a human โ€” me.

๐Ÿ“„ License

MIT ยฉ Peter Lupu


All versions of curs-bnr with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
ext-libxml Version *
ext-simplexml Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package peterlupu/curs-bnr contains the following files

Loading the files please wait ...