Skip to main content

CAP.PRESCAN.SIDE_EFFECT — Side effect in a migration

  • Category: safety
  • Level: 0
  • Stability: stable
  • Suites: lint

This migration would reach outside the database if it ran — a notification, an HTTP request, a queued job, a mail, a cache write. Pretend mode intercepts SQL and nothing else, so the PHP around it runs for real: a lint run that mailed a customer would be the worst thing this tool could do. The migration is reported and never pretend-executed.

PHP's own ways to reach out count the same as the facades: exec(), shell_exec() and a command in backticks, system(), passthru(), proc_open(), popen() and pcntl_exec() start a process as Process does, and mail() and mb_send_mail() send a mail as Mail does.

So does the container. app('cache'), resolve('mailer'), app()->make('queue'), Container::getInstance()->make('events') and App::make('filesystem') hand back what Cache, Mail, Queue, Event and Storage reach, and so do the helpers cache(), session() and broadcast(). Spatie's published create_permission_tables migration ends with app('cache')->store(…)->forget(…), and a lint run that executed it would clear a real cache key. The key must be written as it is, a string or Foo::class: a key computed at run time names nothing a reading of the file can know.

This is not a claim that the effect is wrong — sending a notification is a fine thing for application code to do. It is a claim that a lint run must not be the thing that does it. Move the effect into a deploy step or a job the deploy dispatches. Shadow mode holds the migration back as well, because the effect would leave its throwaway database just the same.

Flagged​

public function up(): void
{
Schema::table('users', function (Blueprint $table): void {
$table->string('plan')->default('free');
});

// A lint run would send this for real, to real people.
Notification::send(User::all(), new PlanChanged);
}

Preferred​

public function up(): void
{
Schema::table('users', function (Blueprint $table): void {
$table->string('plan')->default('free');
});
}

// The announcement is a deploy step, not a schema change. It runs when
// the deploy says so, and a lint run cannot trigger it by looking.