{"record":{"id":"6ad2800dea379623","repo":"phacility/phabricator","slug":"specify-an-absolute-ttl-or-a-relative-ttl-but-not","errorCode":null,"errorMessage":"Specify an absolute TTL or a relative TTL, but not both.","messagePattern":"Specify an absolute TTL or a relative TTL, but not both\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/files/storage/PhabricatorFile.php","lineNumber":1481,"sourceCode":"        'canCDN' => 'optional bool',\n        'profile' => 'optional bool',\n        'format' => 'optional string|PhabricatorFileStorageFormat',\n        'mime-type' => 'optional string',\n        'builtin' => 'optional string',\n        'storageEngines' => 'optional list<PhabricatorFileStorageEngine>',\n        'chunk' => 'optional bool',\n      ));\n\n    $file_name = idx($params, 'name');\n    $this->setName($file_name);\n\n    $author_phid = idx($params, 'authorPHID');\n    $this->setAuthorPHID($author_phid);\n\n    $absolute_ttl = idx($params, 'ttl.absolute');\n    $relative_ttl = idx($params, 'ttl.relative');\n    if ($absolute_ttl !== null && $relative_ttl !== null) {\n      throw new Exception(\n        pht(\n          'Specify an absolute TTL or a relative TTL, but not both.'));\n    } else if ($absolute_ttl !== null) {\n      if ($absolute_ttl < PhabricatorTime::getNow()) {\n        throw new Exception(\n          pht(\n            'Absolute TTL must be in the present or future, but TTL \"%s\" '.\n            'is in the past.',\n            $absolute_ttl));\n      }\n\n      $this->setTtl($absolute_ttl);\n    } else if ($relative_ttl !== null) {\n      if ($relative_ttl < 0) {\n        throw new Exception(\n          pht(\n            'Relative TTL must be zero or more seconds, but \"%s\" is '.\n            'negative.',","sourceCodeStart":1463,"sourceCodeEnd":1499,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/files/storage/PhabricatorFile.php#L1463-L1499","documentation":"PhabricatorFile rejects file-creation parameters that set the file expiry twice: 'ttl.absolute' (a Unix epoch timestamp) and 'ttl.relative' (seconds from now) are two mutually exclusive ways to set a TTL. If both keys hold non-null values, the parameter validation in PhabricatorFile::buildFileFromParams() throws before any storage engine is touched. Only one TTL style may be supplied per file.","triggerScenarios":"Calling PhabricatorFile::newFromParams() (or anything that funnels into buildFileFromParams: the file.upload Conduit method, PhabricatorFileUploadSource, PhabricatorFileQuery writes) with a $params array where both $params['ttl.absolute'] and $params['ttl.relative'] are set. This exact branch runs when idx($params, 'ttl.absolute') !== null AND idx($params, 'ttl.relative') !== null.","commonSituations":"An upload wrapper that injects a default relative TTL (e.g. ttl.relative => 86400) on top of a user-supplied absolute expiry; a refactor from absolute to relative TTL that left the old key in the params array; a Conduit client that copies every field from a form into the request and sends both ttl inputs.","solutions":["Delete one of the two keys from the params array; keep only the TTL style the caller actually wants.","If you merge defaults into user input, only inject the default relative TTL when 'ttl.absolute' is absent, and vice versa (unset the unused key).","In a UI or API client that exposes both options, make them mutually exclusive inputs (radio buttons, or drop one field) before the request reaches the server."],"exampleFix":"// before\n$params = array(\n  'ttl.absolute' => $expires_epoch,\n  'ttl.relative' => 86400,\n);\n$file = PhabricatorFile::newFromParams($params);\n\n// after\n$params = array(\n  'ttl.absolute' => $expires_epoch,\n);\n$file = PhabricatorFile::newFromParams($params);","handlingStrategy":"validation","validationCode":"$has_absolute = (idx($params, 'ttl.absolute') !== null);\n$has_relative = (idx($params, 'ttl.relative') !== null);\nif ($has_absolute && $has_relative) {\n  unset($params['ttl.relative']); // pick one policy explicitly\n}\n$file = PhabricatorFile::newFromParams($params);","typeGuard":"function paramsHaveSingleTtl(array $params): bool {\n  $set = 0;\n  if (idx($params, 'ttl.absolute') !== null) { $set++; }\n  if (idx($params, 'ttl.relative') !== null) { $set++; }\n  return $set <= 1;\n}","tryCatchPattern":"try {\n  $file = PhabricatorFile::newFromParams($params);\n} catch (Exception $ex) {\n  if (preg_match('/not both/', $ex->getMessage())) {\n    unset($params['ttl.relative']);\n    $file = PhabricatorFile::newFromParams($params);\n  } else {\n    throw $ex;\n  }\n}","preventionTips":["Build the params array in one place with an explicit if/else for TTL style instead of merging arrays.","In API clients, make absolute and relative TTL mutually exclusive fields at the schema level.","Add a unit test asserting exactly one ttl key is present in every params array a helper produces."],"tags":["phabricator","file-storage","ttl","parameter-validation","php"],"backgroundTag":"mutually-exclusive-parameters","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}