diff --git a/slic.php b/slic.php index b5abf5c..7360ab6 100644 --- a/slic.php +++ b/slic.php @@ -4,6 +4,7 @@ require_once __DIR__ . '/src/scaffold.php'; require_once __DIR__ . '/src/slic.php'; require_once __DIR__ . '/src/docker.php'; +require_once __DIR__ . '/src/notify.php'; require_once __DIR__ . '/src/plugins.php'; require_once __DIR__ . '/src/themes.php'; require_once __DIR__ . '/src/scripts.php'; @@ -16,6 +17,8 @@ require_once __DIR__ . '/src/codeception.php'; require_once __DIR__ . '/src/commands.php'; +require_once __DIR__ . '/src/classes/Callback_Stack.php'; + use function StellarWP\Slic\args; use function StellarWP\Slic\cli_header; use function StellarWP\Slic\colorize; diff --git a/src/classes/Callback_Stack.php b/src/classes/Callback_Stack.php new file mode 100644 index 0000000..9deac2e --- /dev/null +++ b/src/classes/Callback_Stack.php @@ -0,0 +1,45 @@ + + */ + private $callbacks = []; + + public function call(): void { + foreach ( $this->callbacks as $callback_id => $callback ) { + $callback(); + + // Remove the callback from the stack after it has been called. + $this->remove( $callback_id ); + } + } + + /** + * Add a callback to the stack, with a specified priority. + * + * @param string $callback_id The callback ID. + * @param callable $callback The callback to add. + * + * @return void The callback is added to the stack. + */ + public function add( string $callback_id, callable $callback ): void { + $this->callbacks[ $callback_id ] = $callback; + } + + /** + * Remove a callback from the stack. + * + * @param string $callback_id The callback ID. + * + * @return void The callback is removed from the stack. + */ + private function remove( $callback_id ) { + unset( $this->callbacks[ $callback_id ] ); + } +} diff --git a/src/notify.php b/src/notify.php new file mode 100644 index 0000000..732e675 --- /dev/null +++ b/src/notify.php @@ -0,0 +1,10 @@ +http://localhost:" . getenv( 'WORDPRESS_HTTP_PORT' ) . "" . PHP_EOL ); +} \ No newline at end of file diff --git a/src/services.php b/src/services.php index 97f92c0..b1d0907 100644 --- a/src/services.php +++ b/src/services.php @@ -24,7 +24,7 @@ function stack_schema() { require_once __DIR__ . '/../includes/Spyc/Spyc.php'; $schemas = []; - $stack = slic_stack_array( true ); + $stack = slic_stack_array( true ); foreach ( $stack as $file ) { if ( ! is_readable( $file ) ) { echo magenta( "File $file cannot be found or is not readable." ); @@ -65,6 +65,7 @@ function get_services() { $services = services_schema(); $services = array_keys( $services ); sort( $services ); + return $services; } @@ -79,7 +80,7 @@ function get_services() { * @return array A list of the service dependencies; empty if the service has no * dependencies. */ -function service_dependencies( $service ) { +function service_dependencies( string $service ) { $services_schema = services_schema(); $service_links = isset( $services_schema[ $service ]['links'] ) ? $services_schema[ $service ]['links'] : []; $service_dependencies = isset( $services_schema[ $service ]['depends_on'] ) ? $services_schema[ $service ]['depends_on'] : []; @@ -96,7 +97,7 @@ function service_dependencies( $service ) { * * @return false Whether the specified service requires at least one of the dependencies or not. */ -function service_requires( $service, ...$dependencies ) { +function service_requires( string $service, ...$dependencies ) { if ( empty( $dependencies ) ) { return false; } @@ -109,26 +110,37 @@ function service_requires( $service, ...$dependencies ) { * * @param string $service The service to check. * - * @return Closure A closure that should be called after the service is - * up and running, to finish setting it up. + * @return void The service readiness is ensured; if required + * by the service, a callback that should be called + * after the service is ready is registered in the + * Services callback stack. */ -function ensure_service_ready( $service ) { - $noop = static function(){}; +function ensure_service_ready( string $service ): void { + $propagate_wordpress_address = static function () { + // If wordpress isn't running, there's no IP address to propagate. + if ( ! service_running( 'wordpress' ) ) { + return; + } + + propagate_ip_address_of_to( + [ 'wordpress' ], + [ 'wordpress', 'slic', 'chrome' ], + [ 'wordpress' => 'wordpress.test' ] + ); + }; switch ( $service ) { case 'wordpress': ensure_wordpress_ready(); - service_up_notify( 'wordpress' ); - - return static function () { - propagate_ip_address_of_to( - [ 'wordpress' ], - [ 'wordpress', 'slic', 'chrome' ], - [ 'wordpress' => 'wordpress.test' ] - ); - }; + services_callback_stack()->add( 'propagate_wp_address', $propagate_wordpress_address ); + services_callback_stack()->add( 'wordpress_notify', '\StellarWP\Slic\service_wordpress_notify' ); + break; + case 'slic': + case 'chrome': + services_callback_stack()->add( 'propagate_wp_address', $propagate_wordpress_address ); + break; default: - return $noop; + break; } } @@ -138,12 +150,25 @@ function ensure_service_ready( $service ) { * * @param string $service The service to ensure the dependencies for. * - * @return Closure A closure that should be called after the service is - * up and running to finish setting it up in respect to - * its dependencies. + * @return void + */ +function ensure_service_dependencies( string $service ): void { + ensure_services_running_no_callbacks( service_dependencies( $service ) ); +} + +/** + * Ensures a list of services is running and returns + * a Closure that should be called to finish setting them up. + * + * @param array $services A list of services to ensure + * are running. + * + * @return void On-up callbacks will be registered on the global + * callback stack. */ -function ensure_service_dependencies( $service ) { - return ensure_services_running( service_dependencies( $service ) ); +function ensure_services_running( array $services ): void { + ensure_services_running_no_callbacks( $services ); + services_callback_stack()->call(); } /** @@ -153,10 +178,9 @@ function ensure_service_dependencies( $service ) { * @param array $services A list of services to ensure * are running. * - * @return Closure A closure that should be called after all - * services are up to complete a service setup. + * @return void */ -function ensure_services_running( array $services ) { +function ensure_services_running_no_callbacks( array $services ): void { // Impose an order to make sure dependencies are optimized. $order = [ 'db', 'redis', 'chrome', 'slic', 'wordpress' ]; usort( $services, static function ( $a, $b ) use ( $order ) { @@ -165,18 +189,9 @@ function ensure_services_running( array $services ) { return $a_index <=> $b_index; } ); - $on_up = []; foreach ( $services as $service ) { - $service_on_up = []; - ensure_service_running( $service, [], $service_on_up ); - $on_up = array_merge( $on_up, $service_on_up ); + ensure_service_running_no_callbacks( $service ); } - - return static function () use ( $on_up ) { - foreach ( $on_up as $then ) { - $then(); - } - }; } /** @@ -186,7 +201,7 @@ function ensure_services_running( array $services ) { * * @return bool Whether a service is running or not. */ -function service_running( $service ) { +function service_running( string $service ) { $ps = slic_process()( [ 'ps', '--services', '--filter', '"status=running"' ] ); $ps_status = $ps( 'status' ); @@ -220,8 +235,8 @@ function quietly_tear_down_stack() { * * @return string|null The service container ID if found, `null` otherwise. */ -function get_service_id( $service ) { - $root = root(); +function get_service_id( string $service ) { + $root = root(); $command = "docker ps -f label=com.docker.compose.project.working_dir='$root' " . "-f label=com.docker.compose.service=$service --format '{{.ID}}'"; debug( "Executing command: $command" . PHP_EOL ); @@ -238,7 +253,7 @@ function get_service_id( $service ) { * @return string|null The service IP address if the service is up and the IP address * could be found, `null` otherwise. */ -function get_service_ip_address( $service_id ) { +function get_service_ip_address( string $service_id ) { $command = "docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $service_id"; debug( "Executing command: $command" . PHP_EOL ); exec( $command, $output, $status ); @@ -263,7 +278,7 @@ function get_service_ip_address( $service_id ) { * @return true To indicate the operation was completed correctly, the function * will `exit` with an error, otherwise. */ -function add_hosts_to_service( $service_id, array $hosts ) { +function add_hosts_to_service( string $service_id, array $hosts ) { // Read the current contents of the `/etc/hosts` file. $read_command = "docker exec -u '0:0' $service_id bash -c 'cat /etc/hosts'"; debug( "Executing command: $read_command" . PHP_EOL ); @@ -306,10 +321,10 @@ function add_hosts_to_service( $service_id, array $hosts ) { * Updates the `/etc/hosts` file of a list of services to include the IP address of another set of * services. * - * @param array $of_services The list of services whose IP address should be propagated. - * @param array $to_services The list of services whose `/etc/hosts` file should be updated - * to include the IP addresses to hastname mappings of the previous - * list. + * @param array $of_services The list of services whose IP address should be propagated. + * @param array $to_services The list of services whose `/etc/hosts` file should be updated + * to include the IP addresses to hastname mappings of the previous + * list. * @param array $hostname_map A map from service names to the hostnames they should be * mapped to. * @@ -352,34 +367,48 @@ function propagate_ip_address_of_to( array $of_services, array $to_services, arr * Ensures a service is running by ensuring all its pre-conditions and services * it depends on. * - * @param string $service The name of the service to ensure running, e.g., `wordpress`. + * @param string $service The name of the service to ensure running, e.g., `wordpress`. * @param array $dependencies The list of services that should be running. - * @param array $on_up If provided, the services and dependencies "on up" callbacks - * will not be immediately executed, but instead will be added to - * this list. * * @return int The exit status of the command that will ensure the service is running; * following UNIX convention, a `0` indicates a success, any other value indicates a * failure. */ -function ensure_service_running( $service, array $dependencies = null, array &$on_up = null ) { +function ensure_service_running( string $service, array $dependencies = [] ): int { + $status = ensure_service_running_no_callbacks( $service, $dependencies ); + + services_callback_stack()->call(); + + return $status; +} + +/** + * Ensures a service is running by ensuring all its pre-conditions and services + * it depends on. + * + * @param string $service The name of the service to ensure running, e.g., `wordpress`. + * @param array $dependencies The list of services that should be running. + * + * @return int The exit status of the command that will ensure the service is running; + * following UNIX convention, a `0` indicates a success, any other value indicates a + * failure. + */ +function ensure_service_running_no_callbacks( string $service, array $dependencies = [] ): int { if ( empty( $dependencies ) && service_running( $service ) ) { - service_up_notify( $service ); return 0; } if ( empty( $dependencies ) ) { - $dependencies_on_up = ensure_service_dependencies( $service ); + ensure_service_dependencies( $service ); } else { - $dependencies_on_up = ensure_services_running( $dependencies ); + ensure_services_running_no_callbacks( $dependencies ); } if ( service_running( $service ) ) { - service_up_notify( $service ); return 0; } - $own_on_up = ensure_service_ready( $service ); + ensure_service_ready( $service ); $up_status = slic_realtime()( [ 'up', '-d', $service ] ); service_running( $service ); @@ -388,28 +417,20 @@ function ensure_service_running( $service, array $dependencies = null, array &$o return $up_status; } - if ( is_array( $on_up ) ) { - array_push( $on_up, $dependencies_on_up, $own_on_up ); - } else { - $dependencies_on_up(); - $own_on_up(); - } - - service_up_notify( $service ); - return 0; } /** - * Notifies about the up status of a service. + * Returns the singleton instance of the Services callback stack. * - * @param string $service + * @return Callback_Stack The singleton instance of the Services callback stack. */ -function service_up_notify( string $service ) : void { - switch ( $service ) { - case 'wordpress': - echo colorize( PHP_EOL . "Your WordPress site is reachable at: http://localhost:" . getenv( 'WORDPRESS_HTTP_PORT' ) . "" . PHP_EOL ); - default: - return; +function services_callback_stack(): Callback_Stack { + static $callback_stack; + + if ( ! $callback_stack instanceof Callback_Stack ) { + $callback_stack = new Callback_Stack(); } + + return $callback_stack; } diff --git a/src/slic.php b/src/slic.php index 37aac3e..787d450 100644 --- a/src/slic.php +++ b/src/slic.php @@ -416,6 +416,8 @@ function restart_all_services() { foreach ( $services as $service ) { restart_service( $service ); } + + services_callback_stack()->call(); } /** @@ -424,8 +426,10 @@ function restart_all_services() { function start_all_services() { $services = get_services(); foreach ( $services as $service ) { - ensure_service_running( $service ); + ensure_service_running_no_callbacks( $service ); } + + services_callback_stack()->call(); } /** diff --git a/src/wordpress.php b/src/wordpress.php index e01cede..172b78b 100644 --- a/src/wordpress.php +++ b/src/wordpress.php @@ -43,7 +43,7 @@ function maybe_generate_htaccess() { * * @return bool */ -function dir_has_local_config( $dir ) { +function dir_has_local_config( $dir ): bool { return file_exists( "{$dir}/local-config.php" ); } @@ -54,7 +54,7 @@ function dir_has_local_config( $dir ) { * * @return bool */ -function dir_has_wp_config( $dir ) { +function dir_has_wp_config( $dir ): bool { return file_exists( "{$dir}/wp-config.php" ); } @@ -64,7 +64,7 @@ function dir_has_wp_config( $dir ) { * @return array A map of each directory in the relevant plugins or themes directory to the * corresponding file information. */ -function wp_content_dir_list( $content_type = 'plugins' ) { +function wp_content_dir_list( $content_type = 'plugins' ): array { $function = "\\StellarWP\\Slic\\slic_{$content_type}_dir"; $path = $function(); @@ -101,7 +101,7 @@ static function ( SplFileInfo $file ) { * * @return array Allowed subdirectories for use. */ -function get_allowed_use_subdirectories() { +function get_allowed_use_subdirectories(): array { return [ 'common' ]; } @@ -116,7 +116,7 @@ function get_allowed_use_subdirectories() { * * @return bool Always `true` to indicate files are in place. */ -function ensure_wordpress_files( $version = null ) { +function ensure_wordpress_files( $version = null ): bool { // By default, download the latest WordPress version. $source_url = 'https://wordpress.org/latest.zip'; @@ -210,7 +210,7 @@ function ensure_wordpress_files( $version = null ) { * @return bool Always `true` to indicate WordPress * is set up correctly. */ -function ensure_wordpress_configured() { +function ensure_wordpress_configured(): bool { $wp_root_dir = getenv( 'SLIC_WP_DIR' ); $wp_config_file = $wp_root_dir . '/wp-config.php'; @@ -301,7 +301,7 @@ function ensure_wordpress_configured() { * @return bool Always `true` to indicate WordPress is * correctly installed. */ -function ensure_wordpress_installed() { +function ensure_wordpress_installed(): bool { setup_slic_env( root() ); // Bring up the database. @@ -393,7 +393,7 @@ function ensure_wordpress_installed() { * @return string The current latest version, or `1.0.0` if the information * could not be retrieved. */ -function get_wordpress_latest_version() { +function get_wordpress_latest_version(): string { static $current_latest_version; if ( $current_latest_version !== null ) { @@ -446,7 +446,7 @@ function get_wordpress_latest_version() { * * @return bool Always `true` to indicate success. */ -function ensure_wordpress_ready( $version = null ) { +function ensure_wordpress_ready( string $version = null ): bool { ensure_wordpress_files( $version ); ensure_wordpress_configured(); ensure_wordpress_installed();