定制 phpspec/phpspec-migrate 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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 under DIR, mirroring the relative source sub-directories (spec/App/CalculatorSpec.phpDIR/App/Calculator.spec.php). The originals are kept. Good for a reviewable, side-by-side migration.
  • --in-place — write Foo.spec.php and delete the original FooSpec.php in one step. Because this is destructive it asks for confirmation ([y/N], defaulting to no — press y to proceed); run it on a clean git branch. Cannot be combined with --output-dir. With --dry-run it 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-hard to 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

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-16