*/ protected array $tables = ['pages', 'projects', 'services']; public function up(): void { foreach ($this->tables as $table) { Schema::table($table, function (Blueprint $table) { // The single source of truth for "is this waiting for // somebody". Null means it is not. $table->timestamp('review_requested_at')->nullable(); $table->foreignId('review_requested_by')->nullable()->constrained('users')->nullOnDelete(); // Optional. A review nobody is named for goes to everyone who // could publish it — better than a queue addressed to nobody, // and better than forcing the author to guess who is free. $table->foreignId('reviewer_id')->nullable()->constrained('users')->nullOnDelete(); $table->index('review_requested_at'); }); } Schema::create('review_comments', function (Blueprint $table) { $table->id(); $table->morphs('reviewable'); // Nullable, like a revision's: work sent back by a command or a // scheduled task belongs to nobody, and blaming the last person to // sign in would be worse than saying so. $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); $table->string('user_name')->nullable(); $table->string('event', 20); // Required when sending work back, optional everywhere else. // Returning somebody's work without saying why is the thing that // makes a review process resented. $table->text('body')->nullable(); $table->timestamps(); $table->index(['reviewable_type', 'reviewable_id', 'id']); }); } public function down(): void { Schema::dropIfExists('review_comments'); foreach ($this->tables as $table) { Schema::table($table, function (Blueprint $table) { $table->dropConstrainedForeignKey('review_requested_by'); $table->dropConstrainedForeignKey('reviewer_id'); $table->dropColumn('review_requested_at'); }); } } };