Plugin_State_Controller   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 19
eloc 49
c 5
b 0
f 0
dl 0
loc 184
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A event() 0 23 5
A __construct() 0 3 1
A deactivation() 0 2 1
A has_events_for_state() 0 2 1
A uninstall() 0 2 1
A get_instantiating_file() 0 22 4
A activation() 0 2 1
A get_events_for_state() 0 6 1
A finalise() 0 31 4
1
<?php
2
3
declare( strict_types=1 );
4
5
/**
6
 * Interface for all classes which act on a plugin state change.
7
 * Update, Activation, Deactivation and Uninstalling.
8
 *
9
 * This interface should not be used directory, please see
10
 * those which extend. This is to offer a faux union type.
11
 *
12
 * @package PinkCrab\Plugin_Lifecycle
13
 * @author Glynn Quelch [email protected]
14
 * @since 0.0.1
15
 */
16
17
namespace PinkCrab\Plugin_Lifecycle;
18
19
use PinkCrab\Perique\Application\App_Factory;
20
use PinkCrab\Perique\Interfaces\DI_Container;
21
use PinkCrab\Plugin_Lifecycle\State_Change_Queue;
22
use PinkCrab\Plugin_Lifecycle\Plugin_State_Change;
23
use PinkCrab\Plugin_Lifecycle\State_Event\Uninstall;
24
use PinkCrab\Plugin_Lifecycle\State_Event\Activation;
25
use PinkCrab\Plugin_Lifecycle\State_Event\Deactivation;
26
27
class Plugin_State_Controller {
28
29
	/**
30
	 * Instance of DI_Container
31
	 *
32
	 * @var DI_Container
33
	 */
34
	protected DI_Container $container;
35
36
	/**
37
	 * Hold all events which are fired during the plugins
38
	 * life cycle.
39
	 *
40
	 * @var Plugin_State_Change[]
41
	 */
42
	protected $state_events = array();
43
44
	/**
45
	 * Holds the location of the plugin base file.
46
	 *
47
	 * @var string
48
	 */
49
	protected $plugin_base_file;
50
51
	public function __construct( DI_Container $container, ?string $plugin_base_file ) {
52
		$this->container        = $container;
53
		$this->plugin_base_file = $plugin_base_file ?? $this->get_instantiating_file();
54
	}
55
56
	/**
57
	 * Adds an event to the stack
58
	 *
59
	 * @param class-string<Plugin_State_Change> $state_event
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Plugin_State_Change> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Plugin_State_Change>.
Loading history...
60
	 *
61
	 * @return self
62
	 * @throws Plugin_State_Exception If none Plugin_State_Change (string or object) passed or fails to create instance from valid class name.
63
	 */
64
	public function event( string $state_event ): self {
65
		// Cache the event class name.
66
		$state_event_string = $state_event;
67
68
		/* @phpstan-ignore-next-line, as this cannot be type hinted the check exists. */
69
		if ( ! is_subclass_of( $state_event, Plugin_State_Change::class ) ) {
70
			throw Plugin_State_Exception::invalid_state_change_event_type( $state_event );
71
		}
72
73
		try {
74
			/** @var Plugin_State_Change|null */
75
			$state_event = $this->container->create( $state_event );
76
		} catch ( \Throwable $th ) {
77
			throw Plugin_State_Exception::failed_to_create_state_change_event( $state_event_string );
78
		}
79
80
		// Throw exception if failed to create
81
		if ( null === $state_event || ! is_a( $state_event, Plugin_State_Change::class ) ) {
82
			throw Plugin_State_Exception::failed_to_create_state_change_event( $state_event_string );
83
		}
84
		$this->state_events[] = $state_event;
85
86
		return $this;
87
	}
88
89
	/**
90
	 * Registers all life cycle hooks.
91
	 *
92
	 * @return self
93
	 * @throws Plugin_State_Exception [103] failed_to_locate_calling_file()
94
	 */
95
	public function finalise(): self {
96
97
		$file = $this->plugin_base_file;
98
99
		// Activation hooks if need adding.
100
		if ( $this->has_events_for_state( Activation::class ) ) {
101
			register_activation_hook( $file, $this->activation() );
102
		}
103
104
		// Deactivation hooks.
105
		if ( $this->has_events_for_state( Deactivation::class ) ) {
106
			register_deactivation_hook( $file, $this->deactivation() );
107
		}
108
109
		// If we have an uninstall events, add then during activation.
110
		if ( $this->has_events_for_state( Uninstall::class ) ) {
111
			$callback = $this->uninstall();
112
113
			// Register the callback so itsits included (but wont run due to serialization issues).
114
			register_activation_hook(
115
				$file,
116
				static function () use ( $file, $callback ): void {
117
					register_uninstall_hook( $file, $callback );
118
				}
119
			);
120
121
			// Manually re-add the uninstall hook.
122
			add_action( 'uninstall_' . plugin_basename( $file ), $callback );
123
		}
124
125
		return $this;
126
	}
127
128
	/**
129
	 * Gets all events for a given state.
130
	 *
131
	 * @param string $state
132
	 *
133
	 * @return Plugin_State_Change[]
134
	 */
135
	private function get_events_for_state( string $state ): array {
136
		return array_filter(
137
			apply_filters( Plugin_Life_Cycle::EVENT_LIST, $this->state_events ),
138
			function ( $e ) use ( $state ): bool {
139
				/* @phpstan-ignore-next-line */
140
				return is_subclass_of( $e, $state );
141
			}
142
		);
143
	}
144
145
	/**
146
	 * Checks if they are any events for a given state.
147
	 *
148
	 * @param string $state
149
	 *
150
	 * @return bool
151
	 */
152
	private function has_events_for_state( string $state ): bool {
153
		return count( $this->get_events_for_state( $state ) ) !== 0;
154
	}
155
156
	/**
157
	 * Returns an instance of the State_Change_Queue, populated with Activation events.
158
	 *
159
	 * @return State_Change_Queue
160
	 */
161
	public function activation(): State_Change_Queue {
162
		return new State_Change_Queue( ...$this->get_events_for_state( Activation::class ) );
163
	}
164
165
	/**
166
	 * Returns an instance of the State_Change_Queue, populated with Deactivation events.
167
	 *
168
	 * @return State_Change_Queue
169
	 */
170
	public function deactivation(): State_Change_Queue {
171
		return new State_Change_Queue( ...$this->get_events_for_state( Deactivation::class ) );
172
	}
173
174
	/**
175
	 * Returns an instance of the State_Change_Queue, populated with Uninstall events.
176
	 *
177
	 * @return State_Change_Queue
178
	 */
179
	public function uninstall(): State_Change_Queue {
180
		return new State_Change_Queue( ...$this->get_events_for_state( Uninstall::class ) );
181
	}
182
183
	/**
184
	 * Get the path of the file that instantiated this class.
185
	 *
186
	 * @return string
187
	 * @throws Plugin_State_Exception
188
	 */
189
	protected function get_instantiating_file(): string {
190
191
		$backtrace = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
192
193
		$source_trace = array_values(
194
			array_filter(
195
				$backtrace,
196
				function ( $bt ) {
197
					return array_key_exists( 'class', $bt ) && $bt['class'] === App_Factory::class;
198
				}
199
			)
200
		);
201
202
		if ( 1 !== count( $source_trace ) ) {
203
			throw Plugin_State_Exception::failed_to_locate_calling_file();
204
		}
205
206
		if ( ! \array_key_exists( 'file', $source_trace[0] ) ) {
207
			throw Plugin_State_Exception::failed_to_locate_calling_file();
208
		}
209
210
		return $source_trace[0]['file'];
211
	}
212
}
213