small/class-manipulator

Modern PHP source-code manipulation for classes, interfaces, traits and enums.

Maintainers

Package info

git.small-project.dev/lib/small-class-manipulator.git

pkg:composer/small/class-manipulator

Transparency log

Statistics

Installs: 93

Dependents: 2

Suggesters: 0

3.0.0 2026-08-19 20:29 UTC

This package is auto-updated.

Last update: 2026-08-19 20:30:56 UTC


README

Small Class Manipulator

Small Class Manipulator

Tests        Coverage

Small Class Manipulator reads, creates and rewrites PHP classes, interfaces, traits and enums through an AST-backed API built on nikic/php-parser.

Requirements

  • PHP 8.3 or newer
  • ext-mbstring
  • a Composer project with at least one autoload.psr-4 mapping

Project::open() reads the target project's composer.json and uses its PSR-4 mappings to locate and create PHP types.

Install

The 3.x branch is currently published as the development version 3.x-dev and has not yet been tagged as 3.0.0.

composer require small/class-manipulator:3.x-dev

After a stable 3.0 release is tagged, use:

composer require small/class-manipulator:^3.0

3.x API

use Small\ClassManipulator\Project;

$project = Project::open('/usr/src/my-app');
$class = $project->class(\App\Service\UserService::class);

$class
    ->final()
    ->implements(\App\Contract\UserServiceInterface::class);

$class
    ->property('repository')
    ->private()
    ->type(\App\Repository\UserRepository::class);

$method = $class
    ->method('find')
    ->public()
    ->returns(\App\Entity\User::class);

$method->parameter('id')->type('int');
$method->body('return $this->repository->find($id);');

$class->save();

class(), interface(), trait() and enum() load existing source. The matching createClass(), createInterface(), createTrait() and createEnum() methods create a new type at the path derived from Composer PSR-4 configuration.

Changes remain in memory until save() is called.

Members

method(), property(), constant() and parameter() use get-or-create semantics: an existing member is returned when its name already exists, otherwise it is added to the AST.

$class->property('enabled')
    ->private()
    ->type('bool')
    ->default(true);

$class->constant('VERSION')
    ->public()
    ->type('string')
    ->value('3.0');

$method = $class->method('enable')->public()->returns('void');
$method->body('$this->enabled = true;');

$class->save();

Types also expose imports, attributes, PHPDoc, methods and constants. Classes additionally expose properties, inheritance, implemented interfaces and used traits.

ClassFinder and queries

ClassFinder is a first-class 3.x API in the root Small\ClassManipulator namespace. It returns PhpType objects and delegates filtering to TypeQuery and ClassQuery.

$finder = $project->finder();

$all = $finder->all();
$classes = $finder->classes()->get();
$interfaces = $finder->interfaces()->get();
$traits = $finder->traits()->get();
$enums = $finder->enums()->get();

$services = $finder
    ->classes()
    ->inNamespace('App\\Service')
    ->implementing(\App\Contract\Service::class)
    ->withAttribute(\App\Attribute\Service::class)
    ->get();

Available query filters include:

  • inNamespace() for all type queries
  • withAttribute() for all type queries
  • extending() for class queries
  • implementing() for class queries

Use Project::excludeNamespace() to omit a namespace tree from project discovery.

PHPDoc

$class->phpDoc()
    ->describe('Handles user lookup.')
    ->tag('template', 'T of User');

$class->method('find')->phpDoc()
    ->describe('Find one user.')
    ->param('int', 'id', 'User identifier.')
    ->returns(\App\Entity\User::class, 'Matching user.');

$class->property('repository')->phpDoc()
    ->var(\App\Repository\UserRepository::class);

Existing docblocks can be read and updated through description(), describe(), hasTag(), tagValues(), tag(), removeTag(), param(), returns(), var() and clear().

Creating types

$project->createClass(\App\Service\NewService::class)->save();
$project->createInterface(\App\Contract\NewServiceInterface::class)->save();
$project->createTrait(\App\Support\Reusable::class)->save();
$project->createEnum(\App\Status::class)->save();

Performance

SourceFile reuses the parser and pretty-printer across files, avoiding one parser instance per discovered PHP file.

Project discovery is intentionally not cached: each all() or query get() walks the configured PSR-4 directories and parses the current files from disk. This keeps results fresh when source files are changed outside the current Project instance.

When several filters apply to the same result set, chain them on one query before calling get() so the project is scanned once:

$services = $project
    ->finder()
    ->classes()
    ->inNamespace('App\\Service')
    ->implementing(\App\Contract\Service::class)
    ->withAttribute(\App\Attribute\Service::class)
    ->get();

2.x migration

3.x removes the legacy parser object graph and compatibility API, including ClassFile, ClassManipulator, the old nested ClassFinder filters/collections and Toolbox helpers.

Use Project, ClassFinder, TypeQuery / ClassQuery, and the AST-backed Type, Member, PhpDoc and Source APIs instead.

Development and quality gates

composer test runs both compact API tests and functional project workflows. The functional suite creates temporary Composer projects, writes generated PHP to disk, reloads it and executes generated classes.

PHPStan runs at level: max. Coverage is enforced at 100% for the production 3.x source surface.

composer test
composer phpstan
composer coverage
composer quality