phacility/phabricator · warning · PhutilArgumentUsageException

Atomizer class '%s' must be a concrete subclass of %s.

Error message

Atomizer class '%s' must be a concrete subclass of %s.

What it means

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.

Source

Thrown at src/applications/diviner/workflow/DivinerAtomizeWorkflow.php:51

    $this->readBookConfiguration($args->getArg('book'));

    $console = PhutilConsole::getConsole();

    $atomizer_class = $args->getArg('atomizer');
    if (!$atomizer_class) {
      throw new PhutilArgumentUsageException(
        pht(
          'Specify an atomizer class with %s.',
          '--atomizer'));
    }

    $symbols = id(new PhutilSymbolLoader())
      ->setName($atomizer_class)
      ->setConcreteOnly(true)
      ->setAncestorClass('DivinerAtomizer')
      ->selectAndLoadSymbols();
    if (!$symbols) {
      throw new PhutilArgumentUsageException(
        pht(
          "Atomizer class '%s' must be a concrete subclass of %s.",
          $atomizer_class,
          'DivinerAtomizer'));
    }

    $atomizer = newv($atomizer_class, array());

    $files = $args->getArg('files');
    if (!$files) {
      throw new Exception(pht('Specify one or more files to atomize.'));
    }

    $file_atomizer = new DivinerFileAtomizer();

    foreach (array($atomizer, $file_atomizer) as $configure) {
      $configure->setBook($this->getConfig('name'));
    }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Use a shipped atomizer such as DivinerPHPAtomizer and see if the error disappears
  2. Verify the class exists and is loadable: it must be in a phutil library Diviner can load
  3. If it is your class, make it `final`/non-abstract and declare `extends DivinerAtomizer`
  4. Check the exact spelling and case of the class name passed to --atomizer

Example fix

// before
abstract class ProjectAtomizer extends DivinerAtomizer { }
// invoked as --atomizer ProjectAtomizer (abstract -> rejected)
// after
final class ProjectAtomizer extends DivinerAtomizer {
  protected function executeAtomize(...) { ... }
}
Defensive patterns

Strategy: type-guard

Validate before calling

$atomizer_class = $args->getArg('atomizer');
if (!is_concrete_atomizer_class($atomizer_class)) {
  // Fall back to a known-good shipped atomizer.
  $atomizer_class = 'DivinerPHPAtomizer';
}

Type guard

function is_concrete_atomizer_class($class) {
  if (!is_string($class) || !class_exists($class)) {
    return false;
  }
  if (!is_subclass_of($class, 'DivinerAtomizer')) {
    return false;
  }
  return !(new ReflectionClass($class))->isAbstract();
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/4f04631f3b625023. Report an issue: GitHub.