src/Performance/Preload
Contracts
1 year ago
Exceptions
1 year ago
Limiter
6 months ago
Monitor
1 year ago
Sitemap
1 year ago
State
1 year ago
Traits
1 year ago
Client.php
2.5 KB
1 year ago
Preload_Mode_Manager.php
1.5 KB
1 year ago
Preload_Scheduler.php
8.2 KB
3 months ago
Preloader.php
8.5 KB
1 year ago
Provider.php
9.7 KB
3 months ago
Proxy.php
1.0 KB
1 year ago
Redirect_Client.php
4.0 KB
1 year ago
Trigger_Url.php
1.0 KB
1 year ago
Uri.php
961 B
1 year ago
Provider.php in Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution trunk, at src/Performance/Preload/Provider.php
| 1 | <?php |
| 2 | /** |
| 3 | * The Service Provider for Preloading functionality. |
| 4 | * |
| 5 | * @package SolidWP\Performance |
| 6 | */ |
| 7 | |
| 8 | declare( strict_types=1 ); |
| 9 | |
| 10 | namespace SolidWP\Performance\Preload; |
| 11 | |
| 12 | use SolidWP\Performance\Contracts\Service_Provider; |
| 13 | use SolidWP\Performance\Core; |
| 14 | use SolidWP\Performance\Lock\Contracts\Blockable_Lock; |
| 15 | use SolidWP\Performance\Lock\Lock_Factory; |
| 16 | use SolidWP\Performance\Log\Log; |
| 17 | use SolidWP\Performance\Preload\Contracts\Preloadable; |
| 18 | use SolidWP\Performance\Preload\Limiter\Batch_Limiter; |
| 19 | use SolidWP\Performance\Preload\Limiter\Core_Counter; |
| 20 | use SolidWP\Performance\Preload\Limiter\Sleep_Limiter; |
| 21 | use SolidWP\Performance\Preload\Monitor\Monitor; |
| 22 | use SolidWP\Performance\Preload\Sitemap\Repositories\Contracts\Sitemap_Repository; |
| 23 | use SolidWP\Performance\Preload\Sitemap\Repositories\Sitemap_Option_Repository; |
| 24 | use SolidWP\Performance\Preload\Sitemap\Sitemap; |
| 25 | use SolidWP\Performance\Preload\State\Enums\Source; |
| 26 | use SolidWP\Performance\Preload\State\State; |
| 27 | use SolidWP\Performance\Psr\Log\LoggerInterface; |
| 28 | use SolidWP\Performance\Symfony\Component\HttpClient\HttpClient; |
| 29 | use SolidWP\Performance\Symfony\Component\HttpClient\ScopingHttpClient; |
| 30 | use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; |
| 31 | use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; |
| 32 | use SolidWP\Performance\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; |
| 33 | use SolidWP\Performance\Symfony\Contracts\HttpClient\HttpClientInterface; |
| 34 | use Throwable; |
| 35 | |
| 36 | /** |
| 37 | * The Service Provider for Preloading functionality. |
| 38 | * |
| 39 | * @package SolidWP\Performance |
| 40 | */ |
| 41 | final class Provider extends Service_Provider { |
| 42 | |
| 43 | public const PRELOADER_LOCK = 'solid_performance.preload.preloader_lock'; |
| 44 | |
| 45 | /** |
| 46 | * @inheritDoc |
| 47 | */ |
| 48 | public function register(): void { |
| 49 | $this->register_http_client(); |
| 50 | $this->register_preload_trigger_url(); |
| 51 | $this->register_sitemap_repository(); |
| 52 | $this->register_preload_monitor(); |
| 53 | $this->register_rate_limiter(); |
| 54 | $this->register_preloader(); |
| 55 | $this->register_preload_scheduler(); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Register the underlying Kadence Performance Preloader HTTP Client. |
| 60 | * |
| 61 | * @return void |
| 62 | */ |
| 63 | private function register_http_client(): void { |
| 64 | $this->container->singleton( |
| 65 | HttpClientInterface::class, |
| 66 | function () { |
| 67 | $options = [ |
| 68 | 'headers' => [ |
| 69 | 'User-Agent' => sprintf( |
| 70 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 (compatible; Kadence Performance/%s)', |
| 71 | $this->container->get( Core::PLUGIN_VERSION ) |
| 72 | ), |
| 73 | ], |
| 74 | 'verify_peer' => apply_filters( 'https_local_ssl_verify', false ), |
| 75 | 'verify_host' => apply_filters( 'https_local_ssl_verify', false ), |
| 76 | ]; |
| 77 | |
| 78 | $proxy_url = $this->container->get( Proxy::class )->url(); |
| 79 | |
| 80 | if ( $proxy_url ) { |
| 81 | $options['proxy'] = $proxy_url; |
| 82 | } |
| 83 | |
| 84 | $logger = $this->container->get( LoggerInterface::class ); |
| 85 | |
| 86 | $client = ScopingHttpClient::forBaseUri( HttpClient::create(), home_url(), $options ); |
| 87 | |
| 88 | $redirect_client = new Redirect_Client( $client ); |
| 89 | $redirect_client->setLogger( $logger ); |
| 90 | |
| 91 | return $redirect_client; |
| 92 | } |
| 93 | ); |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Register the URL to use to trigger to a preload batch. |
| 98 | * |
| 99 | * @return void |
| 100 | */ |
| 101 | private function register_preload_trigger_url(): void { |
| 102 | /** |
| 103 | * Filter the URL to use to trigger a preload batch. |
| 104 | * |
| 105 | * @param string $trigger_url The URL to use to trigger a preload batch. |
| 106 | */ |
| 107 | $trigger_url = apply_filters( 'solidwp/performance/preload/trigger_url', home_url( '/' ) ); |
| 108 | |
| 109 | $this->container->when( Trigger_Url::class ) |
| 110 | ->needs( '$trigger_url' ) |
| 111 | ->give( static fn(): string => $trigger_url ); |
| 112 | |
| 113 | $this->container->singleton( Trigger_Url::class, Trigger_Url::class ); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Register the concrete repository to use for the Sitemap Repository. |
| 118 | * |
| 119 | * @return void |
| 120 | */ |
| 121 | private function register_sitemap_repository(): void { |
| 122 | $this->container->when( Sitemap::class ) |
| 123 | ->needs( '$url' ) |
| 124 | ->give( |
| 125 | function (): string { |
| 126 | // We assume this automatically redirects to the correct sitemap. |
| 127 | return home_url( '/sitemap.xml' ); |
| 128 | } |
| 129 | ); |
| 130 | |
| 131 | $this->container->bind( Sitemap_Repository::class, Sitemap_Option_Repository::class ); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Register the preload monitor to check for a stalled preloader. |
| 136 | * |
| 137 | * @return void |
| 138 | */ |
| 139 | private function register_preload_monitor(): void { |
| 140 | /** |
| 141 | * Filter how many seconds to wait before we check for a stalled preloader. |
| 142 | * |
| 143 | * @param int $timeout The timeout in seconds. |
| 144 | */ |
| 145 | $timeout = (int) apply_filters( 'solidwp/performance/preload/monitor/timeout', 12 ); |
| 146 | |
| 147 | /** |
| 148 | * Filter the maximum number of times we retry a stalled preloader before giving up. |
| 149 | * |
| 150 | * @param int $max_retries The number of retries. |
| 151 | */ |
| 152 | $max_retries = (int) apply_filters( 'solidwp/performance/preload/monitor/max_retries', 10 ); |
| 153 | |
| 154 | $this->container->when( Monitor::class ) |
| 155 | ->needs( '$timeout' ) |
| 156 | ->give( static fn(): int => $timeout ); |
| 157 | |
| 158 | $this->container->when( Monitor::class ) |
| 159 | ->needs( '$max_retries' ) |
| 160 | ->give( static fn(): int => $max_retries ); |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Register the rate limiter that controls the batch size of the preloader. |
| 165 | * |
| 166 | * @return void |
| 167 | */ |
| 168 | private function register_rate_limiter(): void { |
| 169 | $this->container->singleton( Core_Counter::class, Core_Counter::class ); |
| 170 | $this->container->singleton( Sleep_Limiter::class, Sleep_Limiter::class ); |
| 171 | $this->container->singleton( Batch_Limiter::class, Batch_Limiter::class ); |
| 172 | |
| 173 | /** |
| 174 | * Filters the maximum load per CPU core before we start rate limiting requests. |
| 175 | * |
| 176 | * 0-0.7 per core is optimal range, allowing headroom. |
| 177 | * 0.7-1.0 per core is high, but manageable. |
| 178 | * 1.0+ per core indicates CPU is likely bottle necked. |
| 179 | * |
| 180 | * @param float $max_load_per_core The maximum load per core before we begin rate limiting preloading. |
| 181 | */ |
| 182 | $max_load_per_core = apply_filters( 'solidwp/performance/preload/max_load_per_core', 0.5 ); |
| 183 | |
| 184 | /** |
| 185 | * Filters the delay multiplier to calculate the sleep time. |
| 186 | * |
| 187 | * @param float $delay The delay multiplier to determine how much more sleep based on system load. |
| 188 | */ |
| 189 | $delay = apply_filters( 'solidwp/performance/preload/sleep_multiplier', 1.5 ); |
| 190 | |
| 191 | $this->container->when( Sleep_Limiter::class ) |
| 192 | ->needs( '$max_load_per_core' ) |
| 193 | ->give( static fn(): float => $max_load_per_core ); |
| 194 | |
| 195 | $this->container->when( Sleep_Limiter::class ) |
| 196 | ->needs( '$delay' ) |
| 197 | ->give( static fn(): float => $delay ); |
| 198 | |
| 199 | /** |
| 200 | * Filter how many URLs are processed per batch. |
| 201 | * |
| 202 | * @param int $batch_size The number of URLs to process per batch. |
| 203 | */ |
| 204 | $batch_size = apply_filters( 'solidwp/performance/preload/batch_size', 50 ); |
| 205 | |
| 206 | /** |
| 207 | * Filter the decay value for how much we'll reduce the batch size when server load is high. |
| 208 | * |
| 209 | * @param float $decay The negative value used to calculate exponential decay. |
| 210 | */ |
| 211 | $decay = apply_filters( 'solidwp/performance/preload/decay', -0.9 ); |
| 212 | |
| 213 | $this->container->when( Batch_Limiter::class ) |
| 214 | ->needs( '$batch_size' ) |
| 215 | ->give( static fn(): int => $batch_size ); |
| 216 | |
| 217 | $this->container->when( Batch_Limiter::class ) |
| 218 | ->needs( '$max_load_per_core' ) |
| 219 | ->give( static fn(): float => $max_load_per_core ); |
| 220 | |
| 221 | $this->container->when( Batch_Limiter::class ) |
| 222 | ->needs( '$decay' ) |
| 223 | ->give( static fn(): float => $decay ); |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * Register the Preloader. |
| 228 | * |
| 229 | * @return void |
| 230 | */ |
| 231 | private function register_preloader(): void { |
| 232 | /** |
| 233 | * Filters how long to sleep between batches. |
| 234 | * |
| 235 | * @param int $seconds How to sleep for in seconds. |
| 236 | */ |
| 237 | $seconds = apply_filters( 'solidwp/performance/preload/batch/sleep/seconds', 1 ); |
| 238 | |
| 239 | $this->container->when( Preloader::class ) |
| 240 | ->needs( '$sleep' ) |
| 241 | ->give( static fn(): int => $seconds ); |
| 242 | |
| 243 | $this->container->singleton( Preloadable::class, Preloader::class ); |
| 244 | |
| 245 | add_action( 'template_redirect', $this->container->callback( Preloadable::class, 'preload' ), 0 ); |
| 246 | |
| 247 | // Automatically try to preload URLs after they are purged. |
| 248 | add_action( |
| 249 | 'solidwp/performance/cache/purge/url/after', |
| 250 | function ( string $url ) { |
| 251 | $preloader = $this->container->get( Preloadable::class ); |
| 252 | |
| 253 | try { |
| 254 | $preloader->preload_url( $url ); |
| 255 | } catch ( TimeoutExceptionInterface |TransportExceptionInterface $e ) { |
| 256 | do_action( |
| 257 | Log::INFO, |
| 258 | 'Timeout for preload {url}', |
| 259 | [ |
| 260 | 'url' => $url, |
| 261 | 'exception' => $e, |
| 262 | ] |
| 263 | ); |
| 264 | } catch ( ClientExceptionInterface $e ) { |
| 265 | do_action( |
| 266 | Log::WARNING, |
| 267 | 'Failed to preload {url}', |
| 268 | [ |
| 269 | 'url' => $url, |
| 270 | 'exception' => $e, |
| 271 | ] |
| 272 | ); |
| 273 | } catch ( Throwable $e ) { |
| 274 | do_action( |
| 275 | Log::ERROR, |
| 276 | 'Failed to preload {url}', |
| 277 | [ |
| 278 | 'url' => $url, |
| 279 | 'exception' => $e, |
| 280 | ] |
| 281 | ); |
| 282 | } |
| 283 | }, |
| 284 | 20, |
| 285 | 1 |
| 286 | ); |
| 287 | } |
| 288 | |
| 289 | /** |
| 290 | * Register the preload scheduler. |
| 291 | * |
| 292 | * @return void |
| 293 | */ |
| 294 | private function register_preload_scheduler(): void { |
| 295 | $this->container->singleton( State::class, State::class ); |
| 296 | |
| 297 | /** |
| 298 | * Filter how long the preloader stays locked until it's canceled or completes. |
| 299 | * |
| 300 | * @param int $ttl How long in seconds the preloader will be locked until it's canceled or completes. |
| 301 | */ |
| 302 | $ttl = apply_filters( 'solidwp/performance/preload/lock/ttl', DAY_IN_SECONDS ); |
| 303 | |
| 304 | $this->container->singleton( |
| 305 | self::PRELOADER_LOCK, |
| 306 | function () use ( $ttl ): Blockable_Lock { |
| 307 | $owner = Source::detect_source(); |
| 308 | |
| 309 | return $this->container->get( Lock_Factory::class )->make( 'is_preloading', $ttl, $owner ); |
| 310 | } |
| 311 | ); |
| 312 | |
| 313 | $this->container->singleton( Preload_Scheduler::class, Preload_Scheduler::class ); |
| 314 | |
| 315 | $this->container->when( Preload_Scheduler::class ) |
| 316 | ->needs( Blockable_Lock::class ) |
| 317 | ->give( |
| 318 | fn(): Blockable_Lock => $this->container->get( self::PRELOADER_LOCK ) |
| 319 | ); |
| 320 | } |
| 321 | } |
| 322 |