{"record":{"id":"4f04631f3b625023","repo":"phacility/phabricator","slug":"atomizer-class-s-must-be-a-concrete-subclass-of","errorCode":null,"errorMessage":"Atomizer class '%s' must be a concrete subclass of %s.","messagePattern":"Atomizer class '(.+?)' must be a concrete subclass of (.+?)\\.","errorType":"console","errorClass":"PhutilArgumentUsageException","httpStatus":null,"severity":"warning","filePath":"src/applications/diviner/workflow/DivinerAtomizeWorkflow.php","lineNumber":51,"sourceCode":"    $this->readBookConfiguration($args->getArg('book'));\n\n    $console = PhutilConsole::getConsole();\n\n    $atomizer_class = $args->getArg('atomizer');\n    if (!$atomizer_class) {\n      throw new PhutilArgumentUsageException(\n        pht(\n          'Specify an atomizer class with %s.',\n          '--atomizer'));\n    }\n\n    $symbols = id(new PhutilSymbolLoader())\n      ->setName($atomizer_class)\n      ->setConcreteOnly(true)\n      ->setAncestorClass('DivinerAtomizer')\n      ->selectAndLoadSymbols();\n    if (!$symbols) {\n      throw new PhutilArgumentUsageException(\n        pht(\n          \"Atomizer class '%s' must be a concrete subclass of %s.\",\n          $atomizer_class,\n          'DivinerAtomizer'));\n    }\n\n    $atomizer = newv($atomizer_class, array());\n\n    $files = $args->getArg('files');\n    if (!$files) {\n      throw new Exception(pht('Specify one or more files to atomize.'));\n    }\n\n    $file_atomizer = new DivinerFileAtomizer();\n\n    foreach (array($atomizer, $file_atomizer) as $configure) {\n      $configure->setBook($this->getConfig('name'));\n    }","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/diviner/workflow/DivinerAtomizeWorkflow.php#L33-L69","documentation":"After `--atomizer <class>` is supplied, the workflow resolves the class with PhutilSymbolLoader using setConcreteOnly(true) and setAncestorClass('DivinerAtomizer'). If the loader selects no symbols — the class does not exist, cannot be loaded, is abstract, or does not extend DivinerAtomizer — this usage exception is thrown. It means the name you gave is not a loadable, concrete DivinerAtomizer subclass.","triggerScenarios":"Passing `--atomizer` with a misspelled class name, an abstract atomizer class, a class that exists but does not extend DivinerAtomizer, or a class in a library not loaded by the Diviner environment (phutil module not in load path).","commonSituations":"Typos in class names in build scripts; custom atomizers written for an older Diviner API whose parent class moved; forgetting to register the custom atomizer's library with phutil so PhutilSymbolLoader cannot discover it; using a namespaced class name where Diviner expects the old-style global name.","solutions":["Use a shipped atomizer such as DivinerPHPAtomizer and see if the error disappears","Verify the class exists and is loadable: it must be in a phutil library Diviner can load","If it is your class, make it `final`/non-abstract and declare `extends DivinerAtomizer`","Check the exact spelling and case of the class name passed to --atomizer"],"exampleFix":"// before\nabstract class ProjectAtomizer extends DivinerAtomizer { }\n// invoked as --atomizer ProjectAtomizer (abstract -> rejected)\n// after\nfinal class ProjectAtomizer extends DivinerAtomizer {\n  protected function executeAtomize(...) { ... }\n}","handlingStrategy":"type-guard","validationCode":"$atomizer_class = $args->getArg('atomizer');\nif (!is_concrete_atomizer_class($atomizer_class)) {\n  // Fall back to a known-good shipped atomizer.\n  $atomizer_class = 'DivinerPHPAtomizer';\n}","typeGuard":"function is_concrete_atomizer_class($class) {\n  if (!is_string($class) || !class_exists($class)) {\n    return false;\n  }\n  if (!is_subclass_of($class, 'DivinerAtomizer')) {\n    return false;\n  }\n  return !(new ReflectionClass($class))->isAbstract();\n}","tryCatchPattern":null,"preventionTips":["Validate the class with class_exists/is_subclass_of before passing it to --atomizer","Keep custom atomizers in a phutil library that Diviner loads","Add a smoke test that loads every class name your scripts pass to --atomizer"],"tags":["diviner","cli","class-loading","usage-error","phabricator"],"backgroundTag":"class-not-subclass","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}