phpspec/phpspec-migrate
Composer 安装命令:
composer create-project phpspec/phpspec-migrate
包简介
Convert legacy phpspec specs (>=8.3, ObjectBehavior style) to phpspec 9.0 (describe/it DSL).
README 文档
README
A command-line tool that converts legacy phpspec specs (ObjectBehavior style, phpspec ≥ 8.3)
into the new phpspec 9.0 closure DSL (describe / it / expect).
It parses each spec with nikic/php-parser (using the PHP version that is running the tool) and rewrites the AST — no fragile regex.
Installation
composer install
Requires PHP 8.2+.
Usage
# Convert a single spec bin/phpspec-migrate spec/App/CalculatorSpec.php # Convert a whole directory (recurses, picks up *Spec.php) bin/phpspec-migrate spec/ # Preview without writing anything bin/phpspec-migrate --dry-run spec/ # Convert even files with constructs that can't be fully translated, # leaving `// TODO [phpspec-migrate]` markers behind bin/phpspec-migrate --try-hard spec/ # Write the converted tree elsewhere (mirrors the source sub-directories) bin/phpspec-migrate --output-dir=spec9 spec/ # Replace the originals in place — deletes each *Spec.php (asks to confirm) bin/phpspec-migrate --in-place spec/
bin/phpspec-migrate --help lists every option.
Where the output goes
There is no destination argument; <path> is always the source. By default each
FooSpec.php is written next to the original as Foo.spec.php, leaving the original in
place so you can diff and verify, then delete it. Two flags change this:
--output-dir=DIR— write the converted tree underDIR, mirroring the relative source sub-directories (spec/App/CalculatorSpec.php→DIR/App/Calculator.spec.php). The originals are kept. Good for a reviewable, side-by-side migration.--in-place— writeFoo.spec.phpand delete the originalFooSpec.phpin one step. Because this is destructive it asks for confirmation ([y/N], defaulting to no — pressyto proceed); run it on a clean git branch. Cannot be combined with--output-dir. With--dry-runit neither prompts nor changes anything.
Use --force to overwrite an existing .spec.php target and -q/--quiet to suppress
the report.
Issues vs. notes (skip vs. try-hard)
The converter reports two kinds of finding:
- Notes — a workaround was applied, but it's lossy or worth a glance (see the limitations below). The file is always converted; the note is printed in the report.
- Issues — a construct that genuinely can't be converted (e.g. an unsupported
Prophecy stub). By default the file is skipped and listed with the reason. Pass
--try-hardto convert it anyway, leaving the construct in place with a// TODO[phpspec-migrate]comment so you can finish it by hand.
What it converts
| Legacy (≤ 8.3) | phpspec 9.0 |
|---|---|
class FooSpec extends ObjectBehavior |
describe(Foo::class, function () { … }) |
function it_does_x(Dep $d) |
it("does x", function (Dep $d) { … }) |
let() + beConstructedWith($a) |
let('foo', fn(Dep $d) => new Foo($d)) |
beConstructedThrough('make', [$a]) |
Foo::make($a) |
in-example $this->beConstructedWith(...) |
$this->foo = new Foo(...) |
$this->method() |
$this->foo->method() |
$this->m()->shouldReturn($v) |
expect($this->foo->m())->toBe($v) |
shouldHaveType / shouldBeAnInstanceOf / shouldImplement |
toBeAnInstanceOf |
shouldBe / shouldEqual / shouldReturn |
toBe |
shouldBeLike |
toBeLike |
shouldNotXxx |
not()->toXxx |
$d->m()->willReturn($v) |
allow($d->m())->toReturn($v) |
$d->m()->willThrow($e) |
allow($d->m())->toThrow($e) |
$d->m()->shouldBeCalled() |
expect($d->m())->toBeCalled() |
$d->m()->shouldHaveBeenCalled() |
expect($d->m())->toHaveBeenCalled() |
Argument::any() / type() / that() / cetera() / exact() |
any() / type() / callback() / cetera() / value |
shouldThrow(E)->during('m', [$a]) |
expect(fn() => $this->foo->m($a))->toThrow(E::class) |
shouldThrow(E)->during('__construct', [...]) |
expect(fn() => new Foo(...))->toThrow(E::class) |
getMatchers() inline matchers |
addMatcher('toName', $callback) |
Limitations & how the tool works around them
These constructs have no clean 1:1 equivalent in the phpspec 9 DSL. The tool applies the following workarounds rather than failing.
Private/protected helper methods — worked around (note)
A helper method has no place inside the describe() closure, so it is extracted to a
top-level function (same name, parameters, return type and body), emitted after the
describe(...) block:
describe(SomeClass::class, function () { // ... examples }); function someHelper() { /* ... */ } // extracted from a private/protected spec method
The file converts normally and a note is recorded. Caveat: if the helper referenced
$this (the subject or collaborators), there is no $this in a free function — review
those by hand.
Argument matchers without a phpspec 9 equivalent — worked around (note)
Matchers such as Argument::containingString() have no counterpart, so they are
replaced with any():
$repo->find(Argument::containingString('x')) // legacy allow($repo->find(any())) // converted
This keeps the spec runnable but is less strict (it matches any argument); a note flags every place where precision was lost so you can tighten it if needed.
Less common Prophecy stubs — not converted (blocking issue)
There is currently no workaround for stubs like will() / willReturnArgument().
Such files are skipped by default and reported; with --try-hard they are converted with
the offending line left in place under a // TODO [phpspec-migrate] comment for you to
finish manually. (willReturn() and willThrow() are supported.)
Development
PRs welcomed!
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-16