{"record":{"id":"3218e2bd3020ff88","repo":"paragonie/random_compat","slug":"randomcompat-substr-second-argument-should-be-an-integer","errorCode":null,"errorMessage":"RandomCompat_substr(): Second argument should be an integer","messagePattern":"RandomCompat_substr\\(\\): Second argument should be an integer","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/byte_safe_strings.php","lineNumber":112,"sourceCode":"         *\n         * @param string $binary_string\n         * @param int $start\n         * @param int|null $length (optional)\n         *\n         * @throws TypeError\n         *\n         * @return string\n         */\n        function RandomCompat_substr($binary_string, $start, $length = null)\n        {\n            if (!is_string($binary_string)) {\n                throw new TypeError(\n                    'RandomCompat_substr(): First argument should be a string'\n                );\n            }\n\n            if (!is_int($start)) {\n                throw new TypeError(\n                    'RandomCompat_substr(): Second argument should be an integer'\n                );\n            }\n\n            if ($length === null) {\n                /**\n                 * mb_substr($str, 0, NULL, '8bit') returns an empty string on\n                 * PHP 5.3, so we have to find the length ourselves.\n                 */\n                /** @var int $length */\n                $length = RandomCompat_strlen($binary_string) - $start;\n            } elseif (!is_int($length)) {\n                throw new TypeError(\n                    'RandomCompat_substr(): Third argument should be an integer, or omitted'\n                );\n            }\n\n            // Consistency with PHP's behavior","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/paragonie/random_compat/blob/b5d188cc9d5e02f94d2c41da23093f1ef557c5b1/lib/byte_safe_strings.php#L94-L130","documentation":"RandomCompat_substr()'s second parameter $start must be an integer byte offset. When it is not (float, string numeric like \"5\", null, bool), the function throws TypeError. This strictness exists because float/string offsets would silently truncate or coerce in native substr and break the deterministic byte-slicing random_compat relies on.","triggerScenarios":"Calling RandomCompat_substr($str, $start) where $start came from float math (e.g. strlen()/2), from a string config value, from JSON with a numeric string, or is null because an optional parameter was never set. Also triggered when using random_int()'s output stored through a middleware that casted to string.","commonSituations":"Dividing lengths to find a midpoint produces a float (32/2 is int in PHP, but ceil()/floor() results are floats); config files (YAML/JSON/env) providing \"0\" as a string; null from optional array keys accessed without defaults; database columns typed as decimal.","solutions":["Cast explicitly with (int) $start once you have confirmed the value is numeric, e.g. $start = (int) $rawStart.","Use is_int($start) validation at your boundary and reject or coerce non-ints.","If the value derives from float math, round intentionally: $start = (int) floor($x).","Provide defaults for optional values: $start = $opts['start'] ?? 0."],"exampleFix":"// before\n$start = $data['offset']; // string \"5\" from JSON\nRandomCompat_substr($str, $start);\n// after\n$start = (int) $data['offset'];\nRandomCompat_substr($str, $start);","handlingStrategy":"validation","validationCode":"$start = $opts['start'] ?? 0;\nif (!is_int($start)) {\n    $start = (int) $start; // only safe if numeric; validate first if untrusted\n}\nRandomCompat_substr($str, $start);","typeGuard":"function isIntOffset($value): bool {\n    return is_int($value) && $value >= 0;\n}\nif (isIntOffset($start)) {\n    $chunk = RandomCompat_substr($str, $start);\n}","tryCatchPattern":"try {\n    $chunk = RandomCompat_substr($str, $start);\n} catch (TypeError $e) {\n    // $start was not an int; coerce or fail\n    $chunk = RandomCompat_substr($str, (int) $start);\n}","preventionTips":["Cast config/JSON-sourced offsets with (int) before use.","Remember ceil()/floor() return floats — cast after rounding.","Provide ?? 0 defaults for optional offset values.","Enable strict_types to surface float/int confusion at call time."],"tags":["php","typeerror","substr","integer-argument"],"backgroundTag":"invalid-argument","analyzedSha":"b5d188cc9d5e02f94d2c41da23093f1ef557c5b1","analyzedAt":"2026-09-13T16:12:09.755Z","contentChangedAt":"2026-09-13T16:12:09.755Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}