Skip to content

Give the specification's JSON Bundler error text with no sequence number - #69

Merged
jwrosewell merged 2 commits into
rcfrom
fix/json-builder-sequence-message
Nov 11, 2026
Merged

jwrosewell merged 2 commits into
rcfrom
fix/json-builder-sequence-message

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

James Rosewell's rule for the 11 November 2026 release is "The point of the release is consistency." When no Sequence Element has produced a sequence number, the JSON Builder fails with an error, and each language that has the error gave it its own wording. PHP said "The JSON Bundler needs a sequence number from the Sequence Element. Place a Sequence Element before the JSON Bundler in the Pipeline." (pipeline/core/src/Messages.php line 53 on rc).

What the specification says

specifications#139, merged into rc as d72eb074, gives the exact text. json-builder.md at d72eb074, lines 120 to 142:

If no Sequence Element has produced a sequence number when the Element runs, the Element MUST fail with an error and MUST NOT set its JSON value. The error MUST carry the message below, which is the text every implementation gives, so that the message reads the same in every language and makes life easier for 51Degrees support.

The JSON Builder needs a sequence number. Place a Sequence Element before the JSON Builder in the Pipeline.

PHP names this Element the JSON Bundler, so PHP gives the same message with JSON Bundler in place of JSON Builder, reading "The JSON Bundler needs a sequence number. Place a Sequence Element before the JSON Bundler in the Pipeline." Nothing else differs. Where an implementation wraps the error in another, such as a Pipeline Configuration Exception that carries the first error as its cause, the error the caller reads carries the message.

The message is the whole text of the error. An implementation MUST NOT add anything to it, such as a link to documentation. A prefix that a language's error type writes in front of every error of that type when the error is shown, such as the type's name, is not part of the message.

So PHP gives "The JSON Bundler needs a sequence number. Place a Sequence Element before the JSON Bundler in the Pipeline."

What changed

Two commits, the test first (977b21c) and then the message (e30f55c). Nothing else changes.

Tests and builds

What remains

specifications#139, which states the text, has merged into rc as d72eb074. The same text goes into the other languages in 51degrees-dotnet#135, 51degrees-java#104, 51degrees-node#74, 51degrees-python#101, 51degrees-go#100 and 51degrees-rust#383. Nothing else is outstanding here.

PHP gives the same text as every other language when no Sequence Element has produced a sequence number, with JSON Bundler in place of JSON Builder, as the JSON Builder page of the specification states.
@jwrosewell
jwrosewell marked this pull request as ready for review November 11, 2026 13:31
@jwrosewell
jwrosewell merged commit b17dcf8 into rc Nov 11, 2026
3 checks passed
@jwrosewell
jwrosewell deleted the fix/json-builder-sequence-message branch November 11, 2026 13:31
jwrosewell added a commit that referenced this pull request Nov 12, 2026
* README: what it does, how to install it and where it came from (#15)

The README now opens with what the code does in the words developers search for, then the install command, try-it links and, where there are any, the repositories it was created from. It ends with a Find out more section, and the logo is a file in the repository. Everything else the README said is kept.

* Check that only rc merges into the default branch (#18)

* Point the IP Intelligence comments at the Python code in 51degrees-python (#21)

* Link the monorepos in place of the old repositories (#20)

Links to the PHP repositories that are now read-only mirrors point at
their folders here, links to the C repositories go to 51degrees-cxx and
the .NET IP Intelligence link goes to 51degrees-dotnet. The test of the
51Did example page expects the page's new source address. One link text
named a repository that is not public and now names the folder.

The History table in README.md, the links to issues and pull requests in
the old repositories and the Packagist step, which needs the mirror's
address, are unchanged.

* Merged Pull Request '#22 update-submodules/rc->rc: Update submodules'

Co-authored-by: Automation51D <51DCI@51Degrees.com>

* Use the data repository for the examples, tests and docs (#23)

* Say in AGENTS.md how to get the data, build and run the examples (#27)

* Link each package's homepage, source and docs, and fix its README links (#28)

* Describe the rc approach to releases in AGENTS.md (#24)

* Let FlowData and pipelines release native memory, and close them in the on-premise tests and examples (#29)

* Check the callback URL before the JavaScript builder writes it (#25)

* Allow only http and https in the callback URL, and test the zone and port rules (#31)

* Run every example on the free data files, and show what needs the Enterprise data file (#35)

* Refuse a 51Did text with white space inside it, or both base64 alphabets (#38)

The 51Did specification says a reader ignores white space around the
base64 text, being any character with the Unicode White_Space property,
and refuses any other character outside the alphabet, white space inside
the text included. It also says a text uses one alphabet, so a text
holding a character from each pair is refused, and that check comes
before `-` and `_` are read as `+` and `/`.

FodId::toStandardBase64 trimmed with trim(), which takes five of the 25
white space characters and NUL as well, and mapped `-` and `_` onto `+`
and `/` with no check for a mix. PHP's strict base64_decode skips spaces,
tabs and line breaks inside the text, so tryFromBase64 and fromBase64
read a mixed text as if it were clean, and read some texts with white
space inside them, depending on how the white space changed the length.

toStandardBase64 now trims the 25 White_Space characters, then answers
null for a text with white space left inside it, with a character from
each alphabet, or that is not UTF-8. tryFromBase64 reports
ParseStatus::InvalidBase64 for such a text, and fromBase64 raises the
OwidException naming InvalidBase64. White space alone still reports
MissingInput.

* Read the on or off php.ini settings the way PHP does, so a bare true is on (#39)

* Read the on or off php.ini settings the way PHP does, so a bare true is on

The Hash extension read allow_unmatched, use_predictive_graph,
use_performance_graph and update_matched_useragent as on only when the
setting's text was exactly "true". PHP's ini parser turns a bare true in
php.ini, or in a -d option, into "1", so a setting written as
`FiftyOneDegreesHashEngine.use_performance_graph = true` was read as off.

The four settings now go through one helper, settingIsOn, which reads the
text with zend_ini_parse_bool, the function PHP uses for its own on or off
settings. "true", "yes" and "on" in any case, and text starting with a
number other than zero such as "1", are on, and anything else is off. A
setting left unset still leaves the C default in place.

CI builds the committed SWIG wrapper rather than generating one, so the
same change is made in hash-php.i and in src/php8/hash_wrap.cxx. The
readme's settings table says which values count as on, and gains the
allow_unmatched row it was missing.

* Test the on or off php.ini settings in a PHP process of their own

Nothing tested the reading of the on or off settings, so CI passed with
or without it. IniSettingsTests starts PHP with the settings on the
command line, as ExamplesWithoutSettingsTests starts the examples,
detects a Chrome User-Agent and reads the match method and the matched
User-Agent. With use_predictive_graph=off and a bare
use_performance_graph=true the method is PERFORMANCE, where PHP stopped
at start-up when true was read as off. update_matched_useragent=1
records the matched characters and off records none. The file is in the
Unit suite that CI runs.

The readme now says, as PHP reads them, that on, yes and true in any
case, and a number other than 0, are on, and that anything else is off.

* Hold the start-up IP Intelligence engine in memory, as device detection does (#40)

* Hold the IP Intelligence data file in memory, as device detection does

PHP IP Intelligence built its engine with the Balanced profile by default,
and let php.ini or the performanceProfile setting pick LowMemory,
BalancedTemp or HighPerformance. Those profiles keep the data file open,
and a PHP process manager forks its workers after the extension has
loaded, so the workers share the file handles and their read positions.
Device detection avoids that by always loading the whole file
(device-detection-php-onpremise issue 38).

The extension now always builds its engine with MaxPerformance, and so
does IpIntelligenceOnPremise when it builds its own. A performance_profile
in php.ini, or a performanceProfile setting, other than MaxPerformance
raises an exception that names the setting, with the wording device
detection uses. The examples that chose LowMemory now use MaxPerformance,
the examples and the readme no longer pass a profile, and OnPremise.md
explains why the whole file is held in memory.

Constants::DEFAULT_PERFORMANCE_PROFILE and Constants::PERFORMANCE_PROFILES
give way to Constants::PERFORMANCE_PROFILE, and
Messages::INVALID_PERFORMANCE_PROFILE to Messages::PERFORMANCE_PROFILE_ERROR.
The committed SWIG wrapper is edited to match ipi-php.i.

* Keep the removed IP Intelligence names as deprecated aliases

Constants::DEFAULT_PERFORMANCE_PROFILE, Constants::PERFORMANCE_PROFILES and
Messages::INVALID_PERFORMANCE_PROFILE come back, so that code written
against them keeps working in a patch release. Each is marked deprecated,
names its replacement, and holds a value true of the engine as it now is.
DEFAULT_PERFORMANCE_PROFILE is MaxPerformance, PERFORMANCE_PROFILES lists
only MaxPerformance, and INVALID_PERFORMANCE_PROFILE equals
PERFORMANCE_PROFILE_ERROR. Nothing in the package uses them.

* Hold only the start-up IP Intelligence engine in memory

The engine the extension builds when PHP starts, from the php.ini
settings, is built before a process manager forks its workers, so it
stays forced to MaxPerformance. An engine IpIntelligenceOnPremise builds
from a dataFile setting is built in the process that uses it and opens
its own file handles, so it takes any profile again, with Balanced as
the default and the same validation as before.

IpIntelligenceOnPremise refuses a performance_profile in php.ini other
than MaxPerformance only when it takes up the start-up engine, as
DeviceDetectionOnPremise does, and the message says an engine built from
a dataFile setting may use any profile. Constants::DEFAULT_PERFORMANCE_PROFILE,
Constants::PERFORMANCE_PROFILES and Messages::INVALID_PERFORMANCE_PROFILE
hold their earlier values again and are no longer deprecated, and
Constants::STARTUP_PERFORMANCE_PROFILE names the start-up engine's
profile.

The console examples, which use the Enterprise data file when they find
one, use LowMemory, apart from the performance example, which measures
speed with MaxPerformance as the .NET one does. The web-mixed example's
per-request engine uses LowMemory again and loses an unused, hard-coded
device detection data file path, the web example says a per-request
engine may use LowMemory, and the readme names the Lite file as the data
repository does, 51Degrees-LiteV41.ipi.

* Drop the unused profile setting from the device detection console example

GettingStartedConsole passed 'performanceProfile' => 'LowMemory' to
DeviceDetectionOnPremise, whose constructor takes no settings, so the
option did nothing and the example always used the engine the extension
builds when PHP starts, which holds the data file in memory. The setting
goes, and the comment says where the engine comes from.

* Test the start-up IP Intelligence engine in a PHP process of its own

The tests of the start-up engine skip in CI, because CI's php.ini names
no data file, and the tests that run would pass on rc too, so putting
the profile switch back into ipi-php.i left CI green.

Two tests in the Integration suite now start PHP with the data_file
setting on the command line, as the device detection examples test does,
and build IpIntelligenceOnPremise from the start-up engine. It is taken
up with the php.ini profile unset or MaxPerformance, and refused with
PERFORMANCE_PROFILE_ERROR for LowMemory. Where /proc/self/fd exists, the
start-up engine holds no handle on the data file, and a LowMemory engine
built from a dataFile setting holds some, which shows the count works.
Elsewhere that test skips and says why. Both use the Lite data file.

The comments in ipi-php.i, its committed wrapper and
IpIntelligenceOnPremise state the reason once, briefly, and no longer
point at an issue.

* Keep the performance example on LowMemory, with MaxPerformance as an option

The performance example uses the Enterprise data file when it finds one,
which is over 6 GB, so it goes back to LowMemory by default, as the other
examples are. run() takes the profile as a fourth argument, and
performance-console.php reads it from an optional third argument, so
MaxPerformance measures the fastest profile where there is memory for
the whole file. The example prints the profile it ran with, and
OnPremise.md shows the command.

* Make each element accept only the Evidence it uses, and none by default (#41)

* Test that each element accepts only the Evidence it uses

* Make each element accept only the Evidence it uses

The JSON bundler, the Set Header element and the cloud aspect engines inherited the default filter that accepts every key, so a Pipeline kept every header in its Evidence and sent it to the cloud service. The JSON bundler now accepts only query.sequence, which it reads, and the other two accept nothing. The Cloud Request Engine fetches the cloud service's evidence keys the first time its filter is asked, so the Evidence the cloud service uses is kept when it is set, before processing. The test cloud service lists the keys the content tests set.

* Test that an element which declares no Evidence accepts none

* Accept no Evidence in an element that declares none, as the specification asks

* Give each web integration element its own switch, put Sequence first and write its Element Data (#45)

* Test the web integration element switches, order and Sequence Element Data

* Give each web integration element its own switch, put Sequence first and write its Element Data

* Name the data provider in the docblock as well, for PHPUnit versions that read annotations

* Use LF line endings in the new test file

* Test that the Sequence Element data is in the JSON sent to the browser

* Declare the Sequence Element's properties and stop the JSON Bundler skipping its data

* Update the tests that counted or compared the Sequence Element's data

* Test the Sequence Element with array, zero and sequence-only evidence

* Read the session id and sequence independently and ignore values that are not strings

* Build the Translation Engine from a configuration file, with the specification's option names (#42)

* Build the Translation Engine from a configuration file

PipelineBuilder::buildFromConfig passes an element's BuildParameters to
its constructor as one settings array, as every configured PHP element
takes them. TranslationEngine's constructor took positional arguments
only, so building it from a configuration file failed with a TypeError.

The constructor now also takes that settings array, with the Translation
Engine specification's option names, SourceElementDataKey (required),
FixedLanguage and MissingTranslationBehavior, or the same names in
camelCase, the style other PHP elements' settings use. The behavior is
matched ignoring case by MissingTranslationBehavior::fromName, and an
unknown one is refused. Sources and translations stay in code only, as
the specification says, and an engine built from settings uses the
element data key translation. The positional arguments, and their names
for named arguments, are unchanged.

Tests in pipeline/translation/tests cover each option, each spelling of
the behavior, an unknown behavior, a missing source, and the positional
and named-argument constructors, and the readme describes the
configuration file.

* Run the Translation Engine tests in CI

ci/packages.ps1 gave pipeline/translation Ci = $false and the package had
no ci folder, so CI never ran its tests. A build log reads "pipeline/
translation has no ci/run-unit-tests.ps1, skipping".

The flag is now true, which makes ci/build-project.ps1 and
ci/install-package.ps1 give the package the vendor folder that loads the
root autoloader, and the new pipeline/translation/ci/run-unit-tests.ps1
runs its tests through common-ci's php/run-unit-tests.ps1, as
pipeline/core's does. That script runs the suite named Unit, so the suite
in phpunit.xml is renamed from Translation, and the notes in
ci/packages.ps1, README.md and AGENTS.md say so.

The flag switches on nothing else. Set-RootAutoload is the only code that
reads it, mirror.yml reads Mirror and Assembled, and nothing in common-ci
reads this repository's package list.

* Record a missing translation as a flow error, and name the behavior argument as the specification does

With the FlowError behavior, a value missing from the map passed the
engine itself where FlowData::setError takes the error, so processing
raised a TypeError and the engine gave none of its properties. The error
is now recorded under the engine's data key, as every other element
records its errors, and the engine's properties are kept.

The sixth constructor argument is now missingTranslationBehavior, the
specification's name, which the configuration file already uses. A
seventh argument, behavior, is kept as a deprecated alias and used in its
place when given, so a caller passing behavior: as a named argument keeps
working. Positional calls are unchanged.

The docblock and the readme no longer credit the specification with
keeping sources and translations out of settings, as it now lets a
package read them from a configuration file. Not reading them is this
engine's choice. The readme says the country translation engines are
named with no BuildParameters, as their constructors take a folder.

New tests process an engine with FlowError and an untranslated value,
build the engine with each named form of the behavior argument, refuse a
setting value that is not text, and process an engine built from a
configuration, which gives no properties.

* Test that log entries and error messages hold only the masked license key

* Show only the last 4 characters of a license key in log entries and error messages

* Correct the masked keys the list test expects

* Generate the Device Detection wrapper with SWIG 4.5.1 (#47)

* Add a script and a check that generate the Device Detection wrapper

* Replace the hand-patched Device Detection wrapper with SWIG 4.5.1 output

* Give EvidenceDeviceDetection its own copies of the inherited map methods

* Regenerate the Device Detection wrapper with the evidence map methods

* Test the evidence map methods, build from the committed wrapper, and correct the dl() note

* Check the callback URL as the specification's six rules say (#44)

* Check the callback URL as the specification's six rules say

Refuse an IPv6 zone identifier, an '@', '?' or '#' anywhere in the host value, and a name ending in a number unless it is an IPv4 address in dotted decimal form. The logged URL shows *** in place of everything in the host before its last '@'.

* Test that a port which is present is not empty

* Refuse a port that is present but empty

A host such as https://example.com otherwise reads as the host https with an empty port. RFC 3986 section 3.2.3 allows an empty port, but section 6.2.3 treats it as equal to none, so the callback URL does not carry one.

* Test callback hosts that end in a line break

* Merged Pull Request '#43 update-example-assets/rc->rc: Update example assets'

Co-authored-by: Automation51D <51DCI@51Degrees.com>

* Show the root logo in every README, add one contributing guide, and fix the README links (#36)

* Show the root logo in every README, add one contributing guide, and fix the README links

Seven READMEs showed an older 51Degrees wordmark from 51degrees.com rather
than the logo the root README shows, and the pipeline 51Did package README
had none. Each now shows the root logo as a Markdown image from
raw.githubusercontent.com, using .github/images/logo-160.png, a 160 pixel
copy of .github/images/logo.png, the size the root README shows it at.

The repository had no contributing guide of its own. One CONTRIBUTING.md
at the root now links the 51Degrees contributing guide.

The Developer Documentation link at the top of four READMEs went to the
main page of the product's generated API reference, which has no content,
and the cloud request engine's and pipeline core's went to the
documentation home page. They now go to the product's documentation.
Pipeline core's link had no space before its title, so GitHub showed it as
text, and now has one. The dependencies page that the on-premise Device
Detection README linked is gone, and the next sentence already links the
tested versions page, so the sentence is removed.

* Link each product's quick start from the READMEs

The documentation links went to section pages of the 51Degrees
documentation, such as _device_detection__index.html, which only list the
pages in the section. They now go to the page in each section that is most
helpful to a developer and the most recently kept up to date. Those are the
Device Detection, IP Intelligence and Reverse Geocoding quick starts, which
show how to choose between the cloud and on-premise and how to start in
each language, and Pipeline Basics for the Pipeline API, which has no quick
start. The campaign tags of each link are unchanged.

* Keep one documentation link where two pointed at the same page

* Package text says what each package does in the words people search for (#16)

* Package text says what each package does in the words people search for

Developers find PHP packages by searching Packagist for words such as
user agent parser, device detection, and IP geolocation. The composer.json
description and keywords of the Device Detection and IP Intelligence
packages now use those words where they are true for the package.

- device-detection/cloud, which Packagist publishes as
  51degrees/fiftyone.devicedetection through the device-detection-php
  mirror, says it is a Device Detection and User-Agent parser that uses
  the 51Degrees cloud service. The keywords that named other companies'
  products are removed.
- device-detection/onpremise says it reads a data file on your server and
  gains the same keywords.
- ip-intelligence says what it finds about an IP address and gains the
  keywords ip-intelligence, ip-geolocation, ip-lookup, and ip-to-country.

Only description and keyword lines change. No version number changes.

* Package text names the products these packages are an alternative to

People who search Packagist for another product's name should find these
packages too. The Device Detection descriptions now end with "An
alternative to UAParser, DeviceAtlas and WURFL." and the IP Intelligence
description with "An alternative to MaxMind and IPinfo."

The keywords wurfl and deviceatlas are back on device-detection/cloud as
they were, and are added to device-detection/onpremise. The
ip-intelligence keywords gain maxmind and ipinfo.

Only description and keyword lines change. No version number changes.

* Name UAParser.js in the Device Detection descriptions

The Device Detection descriptions now end with "An alternative to
UAParser.js, DeviceAtlas, and WURFL." Naming UAParser.js keeps it apart
from the 51Degrees UAParser fork, and the comma before "and" follows the
company writing guide.

Only the two description lines change. No version number changes.

* Set the client-side cache headers, with a weak ETag of the supplied Evidence (#48)

* Test the cache headers of the client-side JavaScript and JSON responses

* Set the client-side cache headers, with a weak ETag of the supplied Evidence and If-None-Match read as RFC 9110 says

* Read the session id from a minified script, and the other process's ETag from its last line

* Set suppressProcessExceptions on the test Pipeline, which the builder does not pass on

* Run the send() test in its own process, as PHP refuses headers once output has started

* Install the pipeline packages from this checkout for the cloud example's Selenium tests

* Check the cloud example serves its client-side script before the Selenium tests start

* Test that an example route serves the script and the JSON through send()

* Test that Vary names every listed header and send() keeps default_mimetype

Vary should name every header in the Pipeline's Evidence key filter, not
only those the request carried, and leave out names that are not field
names. send() should stop PHP's default Content-Type on a 304 or a 412
without changing the default_mimetype setting. The example web tests also
check that the 304 carries no Content-Type.

* Name every header in the Evidence key filter in Vary

The web integration specification asks Vary to name all headers in the
Pipeline's Evidence key filter, so an element whose filter is a list now
names every header in it, whether or not the request carried it. A
filter that is a function cannot list its headers, so such an element
still names the request's headers it accepts. A name that is not an HTTP
field name is left out.

* Stop the default Content-Type without changing default_mimetype

send() set default_mimetype to an empty string for the rest of the
request. PHP reads that setting when it sends the headers, which for a
response with no content is at the end of the request, so it cannot be
restored any earlier. Setting an empty Content-Type and then removing it
stops PHP adding its default and changes no setting.

* Test that the licence spelling of the key is masked in a URL

* Require the core release with LicenseKey, mask the licence spelling, and name the masking literals

* Send only the Evidence the cloud accepts, never resource, license, licence or values (#56)

* Test that reserved names in Evidence are not sent in the request

* Send only the Evidence the cloud accepts, never resource, license, licence or values

* Tidy the reserved Evidence names change after review

* Refuse the Window property names the specification lists as the object name (#53)

* Test that the object name refuses the Window properties the specification lists

* Refuse the Window property names the specification lists as the object name

* List the refused object names in the groups the specification gives

* Write the JSON Bundler keys in lower case (#51)

* Test that the JSON Bundler writes lower case keys

* Write the JSON Bundler's keys in lower case

* Test that the evidenceproperties values are lower case and form a list

* Write the evidenceproperties values in lower case, as a list

* Build a Cloud Aspect Engine whose product is missing, and log the missing product once (#54)

* Test that a Cloud Aspect Engine with a missing product builds and logs once

* Build a Cloud Aspect Engine with a missing product, and log the missing product once

* Test that a missing product is logged at warning level, from the products found, by every Cloud Aspect Engine

* Log a missing product at warning level, from the products found, in every Cloud Aspect Engine

* Do not store or serve a cached Cloud Request Engine response that holds a fodid (#55)

* Test that a Cloud Request Engine response holding a fodid is not cached

* Do not store or serve a cached Cloud Request Engine response that holds a fodid

* Import ElementData

* End the ArrayCache comment with a full stop

* Fail the JSON Bundler when no Sequence Element has produced a sequence number (#50)

* Test that the JSON Bundler fails without a sequence number

* Fail the JSON Bundler, and set no JSON, when no Sequence Element has produced a sequence number

* Give the pipelines in the script tests a sequence number that leaves Evidence alone

* Test that the JSON Bundler reads the integer sequence number in Evidence

* Read the sequence number from Evidence, as an integer, and give the example a Sequence Element

* Add the web request to Evidence as a read-only view under server.http-request (#49)

* Test that the web integration adds server.http-request to Evidence

* Add the web request to Evidence as a read-only view under server.http-request

* Test that the request view's protocol is the request's own, not the Referer's

* Keep the request's own protocol in its view, name the unprefixed headers, and type get()

* Count a sequence only as digits from 0 to 2,147,483,646, with no sign or spaces (#59)

* Test that a sequence counts only as digits from 0 to 2147483646, with no sign or spaces

* Count a sequence only as digits from 0 to 2147483646, and render up to 2147483647

* Say exactly what getSequence renders, with and without a Sequence Element

* Authenticate a Cloud Request Engine on a license key alone, and send the property list (#58)

* Test that log entries and error messages hold only the masked license key

* Show only the last 4 characters of a license key in log entries and error messages

* Correct the masked keys the list test expects

* Test a Cloud Request Engine on a license key alone, with each combination of credentials

* Authenticate a Cloud Request Engine on a license key alone, and send the property list

The engine takes licenseKey and requestedProperties settings. It builds
on a license key with a list, and sends the list as values whenever one
is given, beside a resource key too. It refuses at build only what the
cloud service refuses on every request, being no key at all and a
license key with no resource key or list.

The keys travel in the body of the data request, as resource and
license, and a license key is never put in a URL. The accessible
properties request posts the keys when there is a license key, and a
resource key on its own fetches them as before. The resource key still
names the data route.

When a list was sent, the first answer is compared with it, and the
names the answer left out are logged once as not covered by the keys.

* Test that the licence spelling of the key is masked in a URL

* Require the core release with LicenseKey, mask the licence spelling, and name the masking literals

* Test that a refusal naming the license key is masked, that a missing name is reported as not a property, and that a key is kept as given

* Mask the license key in the cloud's error text, report a missing name as not a property without a license key, keep a key as given, and list the settings

* Expect the configured resource key as resource in the body, and no Evidence value for it

* Add a nullreason only for a null value, and always as a string (#57)

* Test that the JSON Bundler adds a nullreason only for a Property with no value

* Add a nullreason only for a null value, and always as a string

* Test that a plain null has no entry and that a missing value has a reason that explains

* Write null and a nullreason only for a Property assigned no value, and no entry for a plain null

* Put the nullreason constant first in the class

* Write delayexecution only for a Property with a value (#61)

* Test that delayexecution is written only for a Property with a value

* Write delayexecution only for a Property with a value

* Leave the Cloud Request Engine's data out of the JSON Bundler's output (#60)

* Test that the JSON Bundler leaves out the Cloud Request Engine's data

* Leave the Cloud Request Engine's data out of the JSON Bundler's output

* Send every Evidence value but the reserved ones in the cloud request (#65)

* Test that the cloud request carries every Evidence value but the reserved ones

A cloud-only pipeline is built whose single element is the Cloud Request
Engine, so the Engine's own evidence key filter is the only filter the
Evidence is offered to. That filter lists query.User-Agent alone, standing
for the keys the cloud service's evidencekeys answer gives.

The tests ask that an Evidence value the service does not list is still
sent, under the query, header and cookie prefixes, that an accepted value
is still sent beside them, that the reserved names resource, license,
licence and values are left out under any prefix and in any letter case,
and that the web request view under server.http-request is left out.

These tests fail before the change that follows, because Evidence::set
keeps a key only where some element in the pipeline accepts it, so a key
the service does not list never reaches the Engine.

* Send every Evidence value but the reserved ones in the cloud request

Evidence::set kept a key only where some element in the Pipeline accepted
it, and threw the rest away. On a cloud-only pipeline the Cloud Request
Engine is the only element, and its filter is the list of keys the cloud
service's evidencekeys answer gives, so a key the service does not list
never reached the Engine and was never sent.

Evidence::set now puts a key no element accepts in a second collection
instead of dropping it, and the Cloud Request Engine reads both when it
builds its request. A key is held in one collection or the other and never
in both, and a key accepted later replaces the earlier unaccepted copy,
which can happen because an Engine resolves its filter on first use.

getAll is unchanged and still answers with the accepted values alone, so
the element filtered view, the JavaScript served to the browser, the
client side cache key and get() by name all see exactly what they saw
before. The Engine's own evidence key filter is unchanged too, so a web
integration still adds only accepted values, as the web integration
specification requires.

The reserved names resource, license, licence and values were already left
out by addQueryData under any prefix and in any letter case, and the web
request view under server.http-request is held outside the Evidence
values, so neither is sent.

* Expect the cloud request to carry a header no element uses

AcceptedEvidenceTests asserted that a header no element uses is neither
kept nor sent, which is the behaviour the cloud request had before it
carried every Evidence value but the reserved names. The test now expects
that header in the request, and still expects get() to answer null for it,
because only the Cloud Request Engine reads the values no element accepts.

* Test that an accepted value is sent over an unaccepted one for one field

* Say why the unaccepted Evidence values come first in the cloud request

* Test that the web request key is never sent in any letter case

* Leave a value under the web request view's key out of the cloud request

* Rename the data file variable to _51DEGREES_DD_PATH and drop the key name fallbacks (#67)

* Rename the data file environment variable to _51DEGREES_DD_PATH

A POSIX shell cannot set a variable whose name starts with a digit, so
51DEGREES_DD_PATH could not be exported the way the examples described.
Every name now starts with an underscore, and the old name is not read
anywhere.

A new check reads every file git tracks and fails on a name that starts
with the digit, run by its own workflow on each pull request.

* Fail the environment variable check on a fallback name

Each variable has one name and nothing is read in its place, so the check
now also fails on a bare RESOURCE_KEY or LICENSE_KEY read or set as an
environment variable, and on a constant that keeps a legacy name to fall
back to. Only a read, a set or such a constant is matched, so a placeholder
key written between exclamation marks and a longer name ending in the same
words pass.

The 51Did example and live tests, and the Device Detection cloud examples
and tests, still read such names, so the check fails here until they go.

* Read each key from its one environment variable, with no fallback

The 51Did example and live tests read RESOURCE_KEY and LICENSE_KEY when the
_51DEGREES_ names were not set, and the Device Detection cloud examples and
tests read resource_key and the four AcceptCh names in the same way. Each
now reads its _51DEGREES_ name alone, and the CI scripts no longer set the
old names.

The readmes and AGENTS.md now tell a developer to set
_51DEGREES_RESOURCE_KEY, which is the only name the Device Detection cloud
examples read.

* Test the environment variable check, including a lower case name and a file whose name is not ASCII

* Match a name starting with the digit in any case, and read a path that is not ASCII

* Render the Sequence Element's session id and sequence, and 1 and no session id without one (#62)

* Test that without a Sequence Element the script has sequence 1 and no session id

* Render the Sequence Element's session id and sequence, and 1 and no session id without one

* Name the session id test after what it asserts

The test was called testSessionIdFromQueryIsRendered, which described the
behaviour before this branch, when the session id was read from the query
string. It now asserts that the session id comes from the Sequence Element
and is empty without one, so the old name contradicted it.

Only the name changes. The test body and its data provider are untouched,
and nothing else refers to the old name.

* Compare Evidence keys without regard to letter case (#63)

* Test that Evidence keys ignore letter case

* Compare Evidence keys without regard to letter case, in the store and in each filter

* Test that each reader finds an Evidence key in any letter case

Evidence keys ignore letter case, and getAll() hands back each key in the
letters it was first set with, so a reader that indexes the result with a
lower case literal misses the value. Three readers do that today and each
gets a test here, failing before the fix that follows.

The JavaScript builder leaves the session id and the sequence out of the
rendered parameters by an exact match, so a key such as Query.Session-ID
is sent as a parameter as well as in its own field.

The Translation Engine reads its three locale keys by an exact match, so a
header set as Header.Accept-Language resolves no locale and the values are
left untranslated.

IP Intelligence reads query.client-ip and server.client-ip by an exact
match, so a named key in other letters loses its precedence and an address
listed earlier is taken instead.

* Find an Evidence key in any letter case in each of the three readers

evidence.md line 10 says keys are matched without regard to letter case.
Evidence stores a value under the letters its key was first set with, and
getAll() hands those keys back, so each reader now allows for that.

The JavaScript builder lowers the key before matching it against the names
left out of the rendered parameters, so the session id and the sequence are
no longer sent twice.

The Translation Engine reads each of its keys through the Evidence, which
matches without regard to letter case already, rather than indexing what
getAll() returns.

IP Intelligence builds a lowered view of the keys it is given and matches
the two keys it reads by name against that, so those two keep their
precedence whatever letters they carry.

* Test that a key that left the store answers null, and that a key given twice counts once

* Answer null for a key that left the store, and read a key given twice once

* Test that two spellings of a key no element accepts are one key, and that the JavaScript Builder accepts only keys starting with query.

* Keep one spelling of each Evidence key across both collections, and accept only keys starting with query. in the JavaScript Builder

* Read every on-premise value by its build-time property index (#66)

* Test that every on-premise read goes by the build-time property index

A property's index is its place in the list of names the engine was built
with, fixed at build, so the same index names the same property after any
reload from a data file whose property order differs or is a superset.

These tests cover both on-premise engines and fail against the current
code, which reads every value by name. Stubs stand in for the SWIG wrapped
objects, so the tests run without the native extension or a data file.

Device Detection gains a stub engine, result and value under tests/classes
and a SwigDataIndexTests file, registered in phpunit.xml. IP Intelligence's
existing stubs take the build-time name list, and its SwigDataTests gains
the index cases.

* Read every on-premise value by its build-time property index

Both on-premise engines read every value by name, so the native code looked
the name up on every read. A property's index is its place in the list of
names the engine was built with, fixed at build, so the same index names the
same property after any reload, and reading by index saves that lookup.

Each engine now maps its build-time names to their indexes once, from the
engine's own required property list rather than from the data set, whose
property order a reload can change. SwigData reads by that index.

The IP intelligence table is built by initPropertyIndexes, which runs at
construction only, so a refreshData call rebuilds the property metadata from
the new data file and leaves the index table alone.

An engine built with no property names keeps exactly today's behaviour and
reads by name, because native code then gives each property its own place in
the data set as its index, which a reload can move. That half follows once
the native change reaches its second release.

A property in the data set but outside a build-time list has no index, so it
carries the reason rather than a value.

* Test the build-time property index through the native extension, a name given twice, and a setting for one request

* Take the start-up engine's names from the setting PHP started with, and keep the first place of a name given twice

* Name the index test's process helper so it does not override PHPUnit's run

* Close the pipeline and FlowData the index test makes, as the resource check asks

---------

Co-authored-by: 51Degrees <noreply@51degrees.com>

* Give the specification's JSON Bundler error text with no sequence number (#69)

* Test the exact JSON Bundler error text with no sequence number

* Give the specification's JSON Bundler error text with no sequence number

PHP gives the same text as every other language when no Sequence Element has produced a sequence number, with JSON Bundler in place of JSON Builder, as the JSON Builder page of the specification states.

* Move cxx to the 51degrees-cxx release commit 7c4ac436 (#46)

* Move cxx to the 51degrees-cxx rc commit 865cee97 and regenerate the IP Intelligence wrapper

The cxx submodule moves from e866f6d3 (4.5.103) to 865cee97. The
IP Intelligence wrapper is regenerated with SWIG 4.5.1, the version that made
it. The move qualifies the MetaData result ownership settings, so four
CollectionConfig getters are no longer marked as returning a new object.

* Move cxx to the 51degrees-cxx rc commit b4f769ad

Takes the eight 51degrees-cxx pull requests merged into rc after 865cee97, among them the rename of src/common/Date.cpp to date_pp.cpp so it no longer clashes with date.c on a case-insensitive file system.

* Move cxx to the 51degrees-cxx rc commit cfad8978

Takes 51degrees-cxx #130, which writes the text of each IP Intelligence value with its weight in C, and #146, which gives a reason and a message when a property has no value in the matched results.

* Show the reason in the match metrics example when a property has no value

51degrees-cxx #146 gives a property with no value in the matched profile the message "None of the results contain a value for the requested property." where it gave a null message before. AspectPropertyValue throws that message when value is read, and returned null when the message was empty, so the example's listing of every property now stopped at the first such property. The example now prints the message, as the Node example does.

* Move cxx to the 51degrees-cxx release commit 7c4ac436

7c4ac436 is the second cxx release of 11 November, from 51Degrees/51degrees-cxx#150. It carries 51Degrees/51degrees-cxx#148, which keeps the first load's property order for an engine built with no names. No SWIG interface changes beyond a comment, so the committed wrappers stay as they are.

* Test that an engine built with no names reads by index

An engine built with no names takes the names native code gives after the first load as its list. Its table holds every one of them, a reload leaves the table and the names as they were, and a setting given for one request does not change the table. The Device Detection test also times a read by name, a read by index and a build of DeviceDetectionOnPremise on such an engine.

* Read by index on a Device Detection engine built with no names

DeviceDetectionOnPremise now builds its index table from getRequiredProperties whether or not the engine was built with names. Since 51Degrees/51degrees-cxx#148 an engine built with no names gives every property of the first data set, sorted by name, and a reload does not change them, so the index of each one is fixed in the same way as for an engine built with names. SwigData no longer falls back to a read by name for an empty table.

* Read by index on an IP Intelligence engine built with no names

IpIntelligenceOnPremise now builds its index table from getRequiredProperties whether or not the engine was built with names, for the start-up engine and for an engine built from settings. SwigData no longer falls back to a read by name for an empty table, and the startupSetting helper, used only to choose between the two, is removed.

* Time the index table on its own beside the build of the engine class

PHP builds DeviceDetectionOnPremise on every request, and an engine built with no names now builds its index table there, so the timed test also prints how long the table takes to build and how many names it holds.

* Leave an engine built with no names reading by name

This reverts 46e4ea4, c04b489, 8d855c0 and 62531ce. PHP builds DeviceDetectionOnPremise and IpIntelligenceOnPremise on every request, so an engine built with no names would build its index table on every request. In the full build on 62531ce the table of the Lite file's 15 names took 1,345 to 2,563 ns to build, where a read by index saved 24 to 70 ns against a read by name, so a request would have to make about 37 to 74 reads before the time saved covered the table. Reading by name stays the faster approach for such an engine until a decision is made.

* Rank Evidence prefixes in the order evidence.md lists them, and send each name once in the key's own letter case (#70)

* Test that the Cloud Request Engine ranks Evidence prefixes in evidence.md's order

* Rank server, fiftyone and location Evidence in evidence.md's order, then other prefixes alphabetically

* Test that the Cloud Request Engine sends each Evidence name in the key's own letter case, once

* Send each Evidence name in the key's own letter case, once, and restore a missing space

* Put the precedence test helper after the teardown, private after protected

* Give every environment variable one _51DEGREES_ name (#68)

* Make the name check ignore case and fail on the names that remain, red

The fallback pattern kept its letter case only because the IP
Intelligence and location examples and tests read resource_key, which
differs from RESOURCE_KEY by case alone. That name goes in the next
commit, so the pattern now ignores case as the digit-initial one does.

It also fails on FOD_CLOUD_API_URL, IPINTELLIGENCEDATAFILE,
FIFTYONE_DATA_FILE and FIFTYONE_UA_CSV wherever they appear, since none
of them is ever anything but an environment variable, and on
CLOUD_ENDPOINT only where a variable is read, set or named in a
constant, because cloud_endpoint is a pipeline setting as well.

This commit is the check alone, so it fails over the repository as it
stands and names every place the next commit changes.

* Read every environment variable by its one _51DEGREES_ name

resource_key, read by the IP Intelligence, location and cloud request
engine examples and tests, becomes _51DEGREES_RESOURCE_KEY, which the
Device Detection cloud examples already read. That is what let the
check ignore letter case in the commit before this one.

The cloud endpoint was read under two names, FOD_CLOUD_API_URL by the
cloud request engine and the 51Did client, and cloud_endpoint by the
Device Detection cloud examples. Both become _51DEGREES_CLOUD_ENDPOINT,
the name the Rust library already reads. The cloud request engine's
constant is named after the variable, so it becomes
CLOUD_ENDPOINT_VARIABLE rather than carrying a name that starts with an
underscore.

IPINTELLIGENCEDATAFILE becomes _51DEGREES_IPI_PATH, FIFTYONE_DATA_FILE
becomes _51DEGREES_DD_PATH and FIFTYONE_UA_CSV becomes
_51DEGREES_UA_CSV.

The resource_key query string parameter and the cloud_endpoint pipeline
setting carry the same words and are left alone, because only the
environment variable moves.

* Stop the check naming the old variables itself

The four names that are never anything but an environment variable are
matched wherever they appear, so writing them out in the pattern and in
the comment above it made the check fail on its own source. Each is now
built from two pieces, as the digit-initial start already was, and the
comment says what they are rather than spelling them.

* Read the cloud request integration key from its own constant

CloudRequestEngineTestsBase::resourceKey did two jobs, being the fake
key the stubbed responses are keyed on and the name of the environment
variable the integration tests read a real key from. The two are now
separate constants, and the integration tests read
_51DEGREES_RESOURCE_KEY.

The full build caught this, because the check cannot see through a
constant whose own name does not say it holds a variable name.

* Merge rc, and see an old name braced as a PowerShell variable

rc has taken the move to cxx 7c4ac436 and five other pull requests since
this branch was built, and the merged tree has never been built, so it is
built again here. Nothing in what rc brought names an old variable.

The check also misses an old name written as ${env:NAME}, as Env:\NAME or
as a workflow ${{ env.NAME }}, because its prefix set wants the dollar and
the env token adjacent. The CI scripts here are PowerShell, where a name is
braced whenever it sits beside other text, so the check cannot see a form
the files it watches really use. The Go, Node and Java checks all match the
token on its own.

These cases fail until this one does the same.

* Match the env token on its own, so a braced old name is seen

The prefix set wanted the dollar sign and the env token next to each other,
so ${env:NAME}, Env:\NAME and a workflow ${{ env.NAME }} went past it.
Matching the token on its own, with a following backslash allowed, closes
all three and brings this check into line with the Go, Node and Java ones.

* Keep one licence at the root and take the JavaScript template from a submodule (#73)

* Keep one licence at the root, in the form 51degrees-cxx uses

The repository had no licence file at its root, and each of the nine
package folders held its own copy in one of three byte variants of the
header form, which GitHub cannot read as a licence.

LICENSE is now the plain EUPL v1.2 text, byte for byte the file in
51degrees-cxx, data, Go and Rust, and NOTICE holds the two additional
terms that the old files carried at their end and every source header
repeats. The package copies are removed, because Packagist reads the
licence from the license field of composer.json, which every package
declares as EUPL-1.2.

* Take the JavaScript template from a javascript-templates submodule

pipeline/core kept a copy of JavaScriptResource.mustache that the nightly
package update refreshed from an archive of the head of javascript-templates,
with no record of the commit the copy came from.

javascript-templates is now a submodule at the root, with the relative URL
the other language repositories use. The copy stays committed, because
Packagist serves GitHub's archive of a tag and an archive leaves a submodule
empty. The package update moves the submodule to the head of
javascript-templates and copies the templates from it, so the copy and its
commit change together, and the new JavaScript templates check fails when
the copy differs from the submodule.

* Assemble the 51Did package without a licence file of its own

assemble-package.sh copied pipeline/did/LICENSE into the published tree
and stopped when it was missing. The package copies of the licence are
gone, so the published tree carries none, as every other package now
does, and the OWID notice says the rest of the package is EUPL v1.2 as
composer.json declares and each file's header states.

* Give every PHP source file the standard 51Degrees header

Nine IP Intelligence examples and its php-cs-fixer configuration had a
header without the two additional terms, rewrite-composer-json.php had
none, and the Device Detection SWIG interface had the old address. Each
now carries the licenseText of the CopyrightUpdater settings in the tools
repository, with the year it already stated. The seven on-premise IP
Intelligence console examples are left for another open change that
edits them.

* Pin javascript-templates at c773ce22, the commit the copy came from

c773ce22 is the head of javascript-templates main, which the Java, Node
and Python repositories pin, and its JavaScriptResource.mustache is the
committed copy byte for byte.

* Keep the licence in LICENSE alone, with no NOTICE

The two additional terms are stated in the header of every source file,
which is where they apply, so NOTICE is removed and the README says where
the terms are, as in 51degrees-cxx.

* Give the on-premise IP Intelligence console examples the standard header

The seven console examples had a header without the two additional terms.
They now carry the licenseText of the CopyrightUpdater settings in the
tools repository, with the year they already stated, so every PHP source
file has the standard header.

* Build property metadata only when something asks for it, and read every on-premise value by name (#72)

* Test that a request builds no table of every property

PHP builds DeviceDetectionOnPremise, IpIntelligenceOnPremise and the
pipeline on every request, while the native engine is built once, when PHP
starts. These tests say that a request that reads a few values builds
neither the table of every property in the data set nor the pipeline's
database of them, that the table is the same when it is asked for, and
that the database is built by the first search.

CoreTests::testUpdateProperties now searches once before it changes the
element's properties, because the database is taken at the first search
rather than when the pipeline is built, and it still checks that
updatePropertyList brings the change into the database.

* Build the pipeline's property database at the first search

PHP builds the pipeline on every request, and the Pipeline constructor
built the database of every element's property metadata that getWhere
searches, which with the TAC file took 1.6 ms of a 5.1 ms request. The
database is now built when it is first read, so a request that never
searches does not build it. Once built, updatePropertyDatabaseForFlowElement
updates it as before.

* Build Device Detection's table of every property only when asked for

PHP constructs DeviceDetectionOnPremise on every request, while the native
engine is built once, when PHP starts. Each request read the metadata of
every property in the data set (284 in the TAC file), which took 1.6 ms of
a 5.1 ms request.

The engine now builds the table of every property, and the index table,
when they are first read, and a read looks up only its own property in
native code, by the name the engine was built with. A read uses the table
instead once something has built it, and a name the engine was not built
with is looked up in the table, so every answer is the one the table gave.
After a data refresh the table is built again from the new data set.

* Build IP Intelligence's table of every property only when asked for

IpIntelligenceOnPremise is constructed on every request too, and read the
metadata of every available property each time. It now builds the table
and the index table when they are first read, and a read looks up only its
own property, as Device Detection does. When metadata is needed after the
engine is closed, it throws its engine closed message rather than failing.

* Test that every on-premise value is read by name

With the table builds gone, a request through a pipeline that reads by
name is faster than one that reads by index, so these tests say every
value accessor is handed the property's name. The stub tests are those of
the earlier by-name draft. The tests of the index table go, and a property
in the data set that the engine was not built with now carries the native
code's reason for having no value.

* Read every on-premise value by name

Timed over whole requests in one runner job, with the TAC file and the
table builds gone, a request that reads by name is faster than one that
reads by index at 5 and 15 names and level at 1. A read already finds
each property's spelling among the names the engine was built with, so it
hands that name to the native accessor, which looks it up itself, rather
than also finding its index in PHP.

The index table, getPropertyIndex, the not built message and the IP
Intelligence start-up setting reader, which only the index table used, go.
A property in the data set that the engine was not built with is read by
name, so it carries the native code's reason for having no value.

* Test the table fallback and asking a closed engine for a property

The Device Detection test of a property the engine was not built with
now checks that the table of every property was built, which only the
fallback in findProperty does there, and that the no value message is
not the one the missing property service writes.

The IP Intelligence close test asks whether a property exists before
and after the close.

* Ask a closed engine about a name nothing looked up

Run 43176251578 showed the test asking about the name it had already
asked about while the engine was open, which the engine keeps for the
rest of the request, so nothing reached the engine and the test could
not fail for the reason it was written for.

* Answer isset on a closed IP Intelligence engine

Looking a property up needs the engine, which close() releases, so
__isset now answers false rather than letting the engine closed
message out of isset, which cannot throw.

The comment in each findProperty says that the fallback reads the
table through __get, which the isset above it does not.

* Reach the closed engine when asking whether it has a property

Run 43185229593 failed testCloseReleasesResultsAndEngine on the head,
because the pipeline that test builds carries the JSON bundler and the
set header elements, and each reads every property, which builds the
table of every property. The table answers without the engine, so the
read never reached the closed engine and isset answered true.

The closed engine case moves to its own test, whose pipeline leaves
those two elements out, and which asserts the table is still unbuilt so
it cannot pass on the table alone. testCloseReleasesResultsAndEngine
goes back to what it was, covering the default pipeline.

* Let any failure but the engine closed one out of isset

The catch in SwigData::__isset took every exception and answered false,
so a fault reading the native metadata would have read as the engine
having no property of that name. Only the engine closed message now
answers false, and anything else reaches the caller.

Two stub tests pin both halves, and they need no data file, so they run
in every job.

* Keep the old cloud endpoint names for this release, deprecated (#79)

* Test that the old cloud endpoint names are still there

The Cloud Request Engine and the 51Did client read the cloud endpoint
from _51DEGREES_CLOUD_ENDPOINT, and no longer read FOD_CLOUD_API_URL,
the name both published packages read before. Constants::FOD_CLOUD_API_URL
was renamed with nothing left under the old name, and
DidClient::ENDPOINT_VARIABLE kept its name and took the new value.

These tests say what a patch release has to keep. Only the old variable
set gives its endpoint, as does the new one set to nothing. Both set
gives the new one, and neither gives the default. Each old constant is
still there with the value "FOD_CLOUD_API_URL", marked deprecated and
naming the constant that replaces it. The old variable in use is
reported once, through the pipeline's logger by the engine and to the
PHP error log by the 51Did client, which has no logger.

The tests of the environment variable name check now want the old
endpoint name left alone, because the packages read it again.

The tests fail until the fix that follows.

* Keep the old cloud endpoint names for this release, deprecated

A patch release never removes or renames a public name, and that covers
the name of an environment variable a package reads. The Cloud Request
Engine and the 51Did client stopped reading FOD_CLOUD_API_URL, the
constant Constants::FOD_CLOUD_API_URL was renamed with nothing left
under the old name, and DidClient::ENDPOINT_VARIABLE kept its name and
took the new value.

Both now read _51DEGREES_CLOUD_ENDPOINT first and FOD_CLOUD_API_URL when
the new one is not set or is empty, so the new name wins when both are
set. The engine logs the old name in use once, as a warning through the
logger of the first pipeline it is added to. The 51Did package has no
logger, so the client writes it to the PHP error log once in the life
of the process.

Constants::FOD_CLOUD_API_URL is back with the value "FOD_CLOUD_API_URL",
marked @deprecated and naming Constants::CLOUD_ENDPOINT_VARIABLE.
DidClient::ENDPOINT_VARIABLE holds "FOD_CLOUD_API_URL" again, marked
@deprecated, and the new DidClient::CLOUD_ENDPOINT_VARIABLE holds the
new name.

The environment variable name check no longer fails on the old endpoint
name, because the packages read it again. It still fails on every other
old name.

* Say what the old variable name in the engine tests is for

The comment over the test constant said where the endpoint was read from
before, which is history. It now says what the constant holds and why
the tests write the name out.

* Say how often the deprecated variable is reported

The comments said the 51Did client writes the old variable to the PHP
error log once in the life of the process, and that the engine logs it
once. PHP frees a static at the end of every request, and a site builds
its pipeline in every request, so under a web server each is once in
each request. The comments now say that. No code changes.

* Keep every old environment variable name for this release, deprecated (#80)

* Test that the old environment variable names are still read

Every environment variable name that a package, a test or an example
read in the last release is a public name. These tests set only the old
name and expect its value, set both and expect the new name's value, and
expect each constant that held an old name to keep its name and value,
marked deprecated. They fail until the old names are read again.

The tests of the name check expect the old names to be allowed in the
files that read them as deprecated names and nowhere else.

* Leave the cloud endpoint variable unset where the test expects none

An empty value under the new name gives an empty string today and is
not what the test is about, which is that neither name gives an
endpoint.

* Read every old environment variable name again, as a deprecated name

A patch release never removes or renames a public name, and every
environment variable name that a package, a test or an example reads is
a public name. The last release read resource_key, cloud_endpoint, the
four AcceptCh key names, 51DEGREES_DD_PATH, FIFTYONE_DATA_FILE,
FIFTYONE_UA_CSV, FIFTYONE_WRAPPER, IPINTELLIGENCEDATAFILE, RESOURCE_KEY
and LICENSE_KEY, and the IP Intelligence live cloud test read
FOD_CLOUD_API_URL. Each is read again, only when the name that replaced
it is not set or is empty, and its use is reported once. The new
_51DEGREES_ names stay the ones every message and comment gives.

Each constant that held an old name is back under its old name with its
old value, marked deprecated, and a new constant holds the new name.

The IP Intelligence package reads the old data file name when it looks
for the translation files, and writes its use to the PHP error log,
because nothing has a logger at that point. The examples, the test
helpers and the 51Did example server do the same through a small reader
of their own, because each package is published on its own. The fork
safety test writes to standard error. FIFTYONE_WRAPPER named a wrapper
file that the extension no longer needs, so it is reported and nothing
is loaded.

The name check fails on an old name anywhere but the files that read
the old names as deprecated names and the tests of those files, which
it lists. The old cloud endpoint name is checked in the same way again.

* Capture the PHP error log inside each test

PHPUnit 12 points the PHP error log at a file of its own while a test
method runs, so a capture set up before the method saw nothing and the
tests that count the deprecation notices failed with the fix in place.
The log is now pointed at the test's file around the code under test
and put back straight after.

* Say what the name check allows in its comment and its job name

The check's comment still explained a change of letter case by an old
name having gone, which is read again now, and the workflow's job name
said no tracked file names a fallback name, where the files that read
the old names as deprecated names do.

* Keep the deprecation notices out of the test output

Each test that reads a deprecated name on purpose now captures the PHP
error log around the read, so the notice is not printed among the test
results. The translation test makes the package's once-only report
possible again before it counts the lines, so it does not depend on
which test ran first.

* Test that the IP Intelligence package logs the old data file name

A package logs a deprecated name once through its own logger. The
translation engines are given the pipeline's logger when they are added
to a pipeline, so the tests expect one warning there, naming the old
and the new variable, however many pipelines are built, and none when
the new name is set or the translation folder is given to the engines.

* Log the old IP Intelligence data file name through the pipeline's logger

The package wrote the use of IPINTELLIGENCEDATAFILE to the PHP error
log. Its translation engines are given the pipeline's logger when they
are added to a pipeline, so the first of them added after the old name
was read now logs a warning there, once for each time PHP runs the
script. Nothing is written to the PHP error log.

* Test the wording of the IP Intelligence package's deprecation warning

The warning must say that a value was read from the old variable, in
the words the examples and test helpers use, and not that the data file
was read from it, because only the translation folder is looked for
beside the file the variable names.

* Say that a value was read from the old IP Intelligence variable

The package's warning said the data file was read from the variable.
Only the variable's value is read, to look for the translation folder
beside the file it names, so the warning now uses the wording the
examples and test helpers use. The comments that described the warning
say the same.

The fork safety test's comment on the wrapper variable says what the
code does and no longer what it did before.

* IP Intelligence examples use the engine built when PHP starts (#82)

* Test that the IP Intelligence examples use the engine built when PHP starts

Every on-premise example passes the dataFile setting or builds the native
engine itself, so under a web server the data file is opened and re…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant