wpdb = $wpdb; } /** * All known migrations, ordered by version. * * @since 0.1.0 * * @return Migration[] */ public function migrations(): array { $migrations = array( new Migration0001CreateTables(), new Migration0002AddCustomerNames(), new Migration0003AddProductAddons(), ); usort( $migrations, static function ( Migration $a, Migration $b ) { return $a->version() <=> $b->version(); } ); return $migrations; } /** * Installed schema version, 0 when nothing was installed yet. * * @since 0.1.0 * * @return int */ public function current_version(): int { return (int) get_option( self::VERSION_OPTION, 0 ); } /** * Highest schema version known to the code. * * @since 0.1.0 * * @return int */ public function latest_version(): int { $latest = 0; foreach ( $this->migrations() as $migration ) { $latest = max( $latest, $migration->version() ); } return $latest; } /** * Tells whether the database is behind the code. * * @since 0.1.0 * * @return bool */ public function needs_upgrade(): bool { return $this->current_version() < $this->latest_version(); } /** * Runs every pending migration. * * @since 0.1.0 * * @return bool True when the database is up to date afterwards. */ public function migrate(): bool { if ( ! $this->needs_upgrade() ) { return true; } if ( ! $this->acquire_lock() ) { return false; } $ok = true; try { foreach ( $this->migrations() as $migration ) { if ( $migration->version() <= $this->current_version() ) { continue; } $migration->up( $this->wpdb ); update_option( self::VERSION_OPTION, $migration->version(), true ); } delete_option( self::ERROR_OPTION ); } catch ( Throwable $error ) { $ok = false; update_option( self::ERROR_OPTION, $error->getMessage(), false ); } delete_option( self::LOCK_OPTION ); return $ok; } /** * Last migration error, or an empty string. * * @since 0.1.0 * * @return string */ public function last_error(): string { return (string) get_option( self::ERROR_OPTION, '' ); } /** * Takes the migration lock. add_option() is atomic, so only one request wins. * * @return bool */ private function acquire_lock(): bool { if ( add_option( self::LOCK_OPTION, time(), '', false ) ) { return true; } $started = (int) get_option( self::LOCK_OPTION, 0 ); if ( time() - $started > self::LOCK_TTL ) { update_option( self::LOCK_OPTION, time(), false ); return true; } return false; } }