Repository navigation
Release candidate - #76
Merged
Merged
Conversation
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.
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.
Co-authored-by: Automation51D <51DCI@51Degrees.com>
…he on-premise tests and examples (#29)
…erprise data file (#35)
…ets (#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.
…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.
…on 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.
…lt (#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
…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
…ification'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.
* 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 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
… assets' Co-authored-by: Automation51D <51DCI@51Degrees.com>
…ix 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
…or (#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.
…vidence (#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.
…ds 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
…e 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
…-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()
… 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
Show only the last 4 characters of a license key in log entries and error messages
…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
* 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
* Test that delayexecution is written only for a Property with a value * Write delayexecution only for a Property with a value
#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
…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
…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
…ession 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.
* 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
* 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>
…ber (#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 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.
…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
* 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.
…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.
…ry 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.
* 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.
…#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.
This was referenced Nov 12, 2026
Leave the Cloud Request Engine's data out of the JSON Builder's output
51Degrees/51degrees-python#91
Merged
* 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 read in every request. These tests fail until that is fixed. - ExamplesUseStartupEngineTests fails while any example names dataFile or EngineIpiSwig. - ExamplesWithoutSettingsTests runs each console example in its own PHP, started with FiftyOneDegreesIpIntelligenceEngine.data_file naming a free data file, as the Device Detection tests do, and checks an example started without that engine says how to start PHP with it. - ExampleTests runs the examples in this PHP with the engine built when it started. - IpIntelligenceOnPremiseTests checks an engine built from dataFile outside the command line logs one warning through the Pipeline's logger, and that nothing is logged on the command line or for the engine built when PHP starts. Its start-up helper now empties the php.ini data file when it builds from a dataFile setting, so that no engine is built when PHP starts there. * IP Intelligence examples use the engine built when PHP starts Every IP Intelligence on-premise example passed the dataFile setting or built the native engine itself, so under a web server the data file was opened and read in every request. A PHP on-premise engine is built once, when PHP starts, from php.ini, which is what the Device Detection examples already do. - Every on-premise example, console and web, uses the engine built from FiftyOneDegreesIpIntelligenceEngine.data_file. The console scripts check that engine is there and, when it is not, print the command that starts PHP with a data file, found or downloaded as before, and stop. The web-mixed example no longer falls back to building an engine in the request. - CI's php.ini for the package names the free Lite data file, as the Device Detection CI names its data file. - The dataFile setting still works, and its doc comment says what it costs. The messages and the documentation no longer offer it as the way to run. The warning the engine logs for the dataFile setting outside the command line follows in the next commit, so that its tests can be seen failing first. * Close the element the performance and suspicious examples use Both examples take the native engine from an IpIntelligenceOnPremise element, which ResourceCleanupTests requires to be closed in a finally block. The element is now kept and closed there. The engine it uses belongs to the extension and stays loaded. * Test that an IP Intelligence example runs with no data file in php.ini The examples must run out of the box with the free data files. An example started with no FiftyOneDegreesIpIntelligenceEngine.data_file in php.ini now prints how to start PHP with one and stops, so this test, which runs the getting started example with the setting emptied and the Lite file as its argument, fails until the example runs again in a PHP started with that file. * Log once when an IP Intelligence engine is built from dataFile under a web server The dataFile setting still builds a new engine from the data file every time IpIntelligenceOnPremise is constructed, which under a web server is every request. Outside the command line, the first engine built that way now logs a warning through the logger of the first Pipeline it is added to, saying so and naming the php.ini setting that builds the engine once when PHP starts. It is logged once for each time PHP runs the script, which under a web server is once in each request that builds such an engine. * Run an IP Intelligence example again in a PHP started with a data file An on-premise example started with no engine built when PHP started now runs again in a new PHP started with FiftyOneDegreesIpIntelligenceEngine.data_file naming the data file it finds, from its first argument, _51DEGREES_IPI_PATH, assets or the data repository, the free file downloaded when there is none. So every example still runs out of the box with the free data files, and the engine is still built once, when that PHP starts. Where the system gives PHP's own command line, its options, such as one loading the extension, are kept. * Test that dataFile is refused outside the command line Under a web server PHP builds IpIntelligenceOnPremise in every request, so the dataFile setting would load the data file in every request. The new test starts PHP's built-in web server, whose SAPI is cli-server, with the php.ini data file naming the Lite file, and expects a request that passes dataFile to be refused with Messages::DATA_FILE_SETTING_REFUSED, and a request with no settings to use the engine built when PHP started. A second test checks dataFile is still accepted on the command line. These replace the tests of the warning, which the refusal replaces. * Refuse dataFile outside the command line Under a web server PHP builds IpIntelligenceOnPremise in every request, so the dataFile setting would load the data file on every request. Under any SAPI other than cli the setting is now refused with an exception saying so, and naming FiftyOneDegreesIpIntelligenceEngine.data_file in php.ini, which builds the engine once, when PHP starts. On the command line it keeps working, and its doc comment says it is for command-line tools only. This replaces the warning the engine logged for the setting, so the warning's fields, onRegistration, isCommandLine and its message are gone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Releases what has collected on
rcsince the last release, #30, which merged on 4 November 2026 at 21:41 UTC.This release moves PHP toward one behavior across every 51Degrees language. Each element it changes now follows the published 51Degrees specifications, which in turn follow the RFCs they cite, so that the same input gives the same answer in .NET, Java, Node.js, Python, PHP, Go, Rust and C. The on-premise extensions take the C library release 51degrees-cxx 4.5.105.
Before you upgrade
getEvidenceKeyFilteraccepted every key and now accepts none, so$flowData->evidence->get()givesnullfor a key that no element accepts. OverridegetEvidenceKeyFilterand return aBasicListEvidenceKeyFilterthat lists the keys the element reads (#41, #50).valueon an on-premise Property with no value throws. The C library now gives the message "None of the results contain a value for the requested property.", where it gave none, andAspectPropertyValuethrows its message whenvalueis read. Code that gotnullnow gets an\Exception, so testhasValuebefore readingvalue(#46).FiftyOneDegreesIpIntelligenceEngine.performance_profileother thanMaxPerformancenow makesIpIntelligenceOnPremisethrow, so remove that setting (#40).trueas on.FiftyOneDegreesHashEngine.allow_unmatched,use_predictive_graph,use_performance_graphandupdate_matched_useragentcounted only"true"in quotes as on. A php.ini that wrote a baretrue,on,yesor1got off and now gets on, which can change detection results, so check the four settings (#39).resource,license,licenceandvalues, which it writes itself, so do not add Evidence that must not reach the cloud service. Each name is sent in the key's own letter case, socookie.51D_Experimentis sent as51D_Experiment, where it was51d_experiment(#56, #65, #70).JsonBundlerElementfails with the message "The JSON Bundler needs a sequence number. Place a Sequence Element before the JSON Bundler in the Pipeline." when there is none.PipelineBuilder::build()adds the Sequence Element first.PipelineBuilder::buildFromConfig()adds none, so a configuration file that listsJsonBundlerElementmust listSequenceElementbefore it (#50, #69).sequenceentry that holdssession-idandsequence, and loses thecloudentry, which held the cloud service's whole response. Every key is lower case apart fromjavascriptProperties. Anullreasonis written only for a Property with no value, sofalse,0and an empty string no longer get one (#45, #60, #51, #57).JavascriptBuilderElementthrows anInvalidArgumentExceptionfor a protocol other thanhttporhttps, and for an object name such aswindowordocument(#31, #53). ACloudRequestEngineresourceKeythat is empty or only white space counts as absent (#58).FiftyOneDegreesHashEngine.phpis gone, because the extension registers its classes itself. Remove anyrequireof that file, and enable the extension in php.ini, because nothing callsdl()to load it any more (#47).refreshData(). A Property that only a later Data File holds has no value until the Engine is created again, which for the Engine built when PHP starts means restarting PHP (#46).InvalidBase64.FodId::toStandardBase64returnsnullfor such a text, and its return type is now?string(#38)._51DEGREES_CLOUD_ENDPOINTreplacesFOD_CLOUD_API_URL, which still works in this release. The Cloud Request Engine and the 51Did client read their endpoint from_51DEGREES_CLOUD_ENDPOINT, and readFOD_CLOUD_API_URLonly when the new name is unset or empty. When the old name is used, the Cloud Request Engine logs a deprecation warning through the Pipeline's logger, and the 51Did client writes the same sentence to the PHP error log.Constants::FOD_CLOUD_API_URLandDidClient::ENDPOINT_VARIABLEstill holdFOD_CLOUD_API_URLand are deprecated, andConstants::CLOUD_ENDPOINT_VARIABLEandDidClient::CLOUD_ENDPOINT_VARIABLEhold the new name. Set_51DEGREES_CLOUD_ENDPOINTto the same value as the old name (#68, #79).dataFilesetting is refused outside the command line.IpIntelligenceOnPremisebuilt a new Engine from the Data File it names, so a web application that passed it loaded the Data File, several gigabytes for the Enterprise file, in every request. Under any server API other thancliit now throws an exception that says the setting is refused outside the command line and names the php.ini setting to use. SetFiftyOneDegreesIpIntelligenceEngine.data_filein php.ini, which builds the Engine once when PHP starts, and removedataFilefrom the web application (IpIntelligenceOnPremise.php lines 178 to 181, #82).What changes
Web integration and client-side caching
Utils::javascriptResponse()andUtils::jsonResponse()answer a client-side JavaScript or JSON endpoint withCache-Control,Varyand an ETag. PHP has no web integration package, so call them from your own endpoints, as the web examples do (#48).W/"...", andIf-None-Matchis compared weakly, as RFC 9110 section 8.8.3 and section 13.1.2 ask. A match gets 304 for GET and HEAD, and 412 for any other method. A response to a request whose processing failed hasCache-Control: private, no-cacheand no ETag. The same Evidence gives the same ETag in every process (#48).PipelineBuildergains the settingsaddSequenceElement,addJsonBundlerElement,addJavaScriptBuilderElementandaddSetHeaderElement, each true by default (#45).Evidence::setFromWebRequest()adds the web request as a read-only view underserver.http-request(#49).JavaScript Builder
httporhttps, and the host must be ASCII labels or an IPv6 address in brackets, with no user information, query, fragment or zone identifier, following RFC 3986. A URL that fails is left out of the script and logged (#31, #44).Evidence
header.User-Agentandheader.user-agentare one key (#63).Cloud
licenseKey. With a License Key alone the Properties wanted must be named withrequestedProperties. Both keys are sent in the request body (#58).query, thenheader,cookie,server,fiftyoneandlocation(#70).*(#52).IpIntelligenceCloudandGeoLocationCloudthrew on every request (#54).setCache(#55).Device Detection and IP Intelligence
Translation
TranslationEnginecan be built from a configuration file, with the specification's option namesSourceElementDataKey,FixedLanguageandMissingTranslationBehavior. The named argumentbehavior:still works and is deprecated (#42).C library
dataFilesetting with noconcurrencytakes the C library's default, which is now the processor count, where it was 10. For51D_GetHighEntropyValuesand51D_StructuredUserAgentthe query value is used before the cookie value (#46).Packages and platforms
fiftyone.pipeline.cloudrequestenginerequiresfiftyone.pipeline.core4.5.207 or later (#52).config.m4no longer runs SWIG (#47).LICENSEfile at its root, the EUPL 1.2 text. The packages no longer carry aLICENSEfile of their own, and each still declaresEUPL-1.2incomposer.json(#73).Examples and documentation
FiftyOneDegreesIpIntelligenceEngine.data_filein php.ini, as the Device Detection examples already did (#82).Environment variables
Each name below has a new name that starts
_51DEGREES_, which every shell can set. The Cloud Request Engine and the 51Did client read the cloud endpoint, the IP Intelligence package reads the folder of its translation files, and the examples and tests read the rest. Every old name in the table is still read in this release, but only when its new name is unset or empty. Using an old name is reported as deprecated, through the Pipeline's logger in the Cloud Request Engine and the IP Intelligence package, and in the PHP error log or on standard error in the 51Did client, the examples and the tests. Each constant that held an old name still holds it and is deprecated (#67, #68, #79, #80).FOD_CLOUD_API_URL,cloud_endpoint_51DEGREES_CLOUD_ENDPOINT51DEGREES_DD_PATH,FIFTYONE_DATA_FILE_51DEGREES_DD_PATHIPINTELLIGENCEDATAFILE_51DEGREES_IPI_PATHresource_key,RESOURCE_KEY_51DEGREES_RESOURCE_KEYLICENSE_KEY_51DEGREES_LICENSE_KEYAcceptChPlatformKey,AcceptChHardwareKey,AcceptChBrowserKey,AcceptChNoneKey_51DEGREES_RESOURCE_KEY_ACCEPTCH_PLATFORM,_51DEGREES_RESOURCE_KEY_ACCEPTCH_HARDWARE,_51DEGREES_RESOURCE_KEY_ACCEPTCH_BROWSER,_51DEGREES_RESOURCE_KEY_ACCEPTCH_NONEFIFTYONE_UA_CSV_51DEGREES_UA_CSVTwo old names have no new name. The Device Detection fork safety test reports
FIFTYONE_WRAPPERas deprecated and loads nothing, because the extension now registers the classes that the wrapper file held (#47, #80).DEVICEDETECTIONDATAFILEis no longer read, because the IP Intelligence mixed web example read it into a variable that nothing used, so setting it never changed anything (#80).Fixed
trueforFiftyOneDegreesHashEngine.use_predictive_graphstopped PHP when it started (#39).FlowErrorbehavior raised aTypeErrorfor a value with no translation (#42).evidencepropertiesas an object, which the client script cannot read, when the first Evidence Property was not delayed (#51).Query.Session-ID, was written into the client script twice, so the script's snippets ran again on every page (#63).PipelineBuilder::build()twice added the Sequence Element, the JSON Builder, the JavaScript Builder and the Set Headers Element twice (#45).refreshData(),DeviceDetectionOnPremisekept the Property metadata of the Data File it was constructed with (#72).ConfigIpigetters was called (#46).Issues this closes
None.
Build
Full build 43314252769 is green, with macOS. It ran from 21:50 to 22:10 UTC on 12 November 2026 on
tmp/pr-matrix-76rel2. All 14Setting 'PullRequestSha'lines, being Configure and the 13 Build and Test jobs, readab719c7705aa4b7199d086b2cb0b12afdb855fae, which is this pull request's head and the head ofrc, so every job built this tree and nothing else. The Build and Test jobs are Ubuntu PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 and 8.5, macOS PHP 8.1, 8.2, 8.3, 8.4 and 8.5, and Windows PHP 8.3. Every one of them succeeded, as did Configure, Compare Performance and Complete. Report Compare Performance was skipped because Compare Performance passed.Compare Performance is within its history in all ten configurations it measures. The floor is the average of the last 10 results less the larger of two standard deviations and 10 per cent of the average.
Eight of the ten results are above their averages, so this head is not slower than its history. The two below are inside the spread of their history. macOS PHP 8.3 is 0.91 of a standard deviation under its average, and Ubuntu PHP 8.2 is 0.03 of a standard deviation under its average.
rcis final for this release. The last change was #82, merged at 21:50 UTC, which makes the IP Intelligence examples use the Engine built when PHP starts and refusesdataFileoutside the command line.rcalso holds the read by name (#72), which specifications#141 allows, and every old environment variable name read again as a deprecated name (#79, #80). Six drafts intorcstay open and are not in this release. #71 is superseded by #72 and is to be closed, not merged. #78 is a temporary red run for #72 and must not merge. #26, #37 and #77 wait for a later release. #81, which points AGENTS.md at the one list of environment variable names, is held until this release merges.Only the patch number moves.