| @@ -1,11 +1,14 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Yoast\WP\SEO\Builders; |
| 4 | 4 | |
| 5 | +use Throwable; | |
| 6 | +use Yoast\WP\SEO\Exceptions\Indexable\Indexing_Failed_Exception; | |
| 5 | 7 | use Yoast\WP\SEO\Exceptions\Indexable\Not_Built_Exception; |
| 6 | 8 | use Yoast\WP\SEO\Exceptions\Indexable\Source_Exception; |
| 7 | 9 | use Yoast\WP\SEO\Helpers\Indexable_Helper; |
| 10 | +use Yoast\WP\SEO\Loggers\Logger; | |
| 8 | 11 | use Yoast\WP\SEO\Models\Indexable; |
| 9 | 12 | use Yoast\WP\SEO\Repositories\Indexable_Repository; |
| 10 | 13 | use Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager; |
| 11 | 14 | |
| @@ -107,8 +110,15 @@ | ||
| 107 | 110 | */ |
| 108 | 111 | protected $version_manager; |
| 109 | 112 | |
| 110 | 113 | /** |
| 114 | + * The logger. | |
| 115 | + * | |
| 116 | + * @var Logger | |
| 117 | + */ | |
| 118 | + protected $logger; | |
| 119 | + | |
| 120 | + /** | |
| 111 | 121 | * Returns the instance of this class constructed through the ORM Wrapper. |
| 112 | 122 | * |
| 113 | 123 | * @param Indexable_Author_Builder $author_builder The author builder for creating missing indexables. |
| 114 | 124 | * @param Indexable_Post_Builder $post_builder The post builder for creating missing indexables. |
| @@ -121,8 +131,9 @@ | ||
| 121 | 131 | * @param Primary_Term_Builder $primary_term_builder The primary term builder for creating primary terms for posts. |
| 122 | 132 | * @param Indexable_Helper $indexable_helper The indexable helper. |
| 123 | 133 | * @param Indexable_Version_Manager $version_manager The indexable version manager. |
| 124 | 134 | * @param Indexable_Link_Builder $link_builder The link builder for creating missing SEO links. |
| 135 | + * @param Logger $logger The logger. | |
| 125 | 136 | */ |
| 126 | 137 | public function __construct( |
| 127 | 138 | Indexable_Author_Builder $author_builder, |
| 128 | 139 | Indexable_Post_Builder $post_builder, |
| @@ -134,9 +145,10 @@ | ||
| 134 | 145 | Indexable_Hierarchy_Builder $hierarchy_builder, |
| 135 | 146 | Primary_Term_Builder $primary_term_builder, |
| 136 | 147 | Indexable_Helper $indexable_helper, |
| 137 | 148 | Indexable_Version_Manager $version_manager, |
| 138 | - Indexable_Link_Builder $link_builder | |
| 149 | + Indexable_Link_Builder $link_builder, | |
| 150 | + Logger $logger | |
| 139 | 151 | ) { |
| 140 | 152 | $this->author_builder = $author_builder; |
| 141 | 153 | $this->post_builder = $post_builder; |
| 142 | 154 | $this->term_builder = $term_builder; |
| @@ -148,8 +160,9 @@ | ||
| 148 | 160 | $this->primary_term_builder = $primary_term_builder; |
| 149 | 161 | $this->indexable_helper = $indexable_helper; |
| 150 | 162 | $this->version_manager = $version_manager; |
| 151 | 163 | $this->link_builder = $link_builder; |
| 164 | + $this->logger = $logger; | |
| 152 | 165 | } |
| 153 | 166 | |
| 154 | 167 | /** |
| 155 | 168 | * Sets the indexable repository. Done to avoid circular dependencies. |
| @@ -253,10 +266,10 @@ | ||
| 253 | 266 | |
| 254 | 267 | /** |
| 255 | 268 | * Ensures we have a valid indexable. Creates one if false is passed. |
| 256 | 269 | * |
| 257 | - * @param Indexable|false $indexable The indexable. | |
| 258 | - * @param array $defaults The initial properties of the Indexable. | |
| 270 | + * @param Indexable|false $indexable The indexable. | |
| 271 | + * @param array<string, int|string> $defaults The initial properties of the Indexable. | |
| 259 | 272 | * |
| 260 | 273 | * @return Indexable The indexable. |
| 261 | 274 | */ |
| 262 | 275 | protected function ensure_indexable( $indexable, $defaults = [] ) { |
| @@ -301,15 +314,15 @@ | ||
| 301 | 314 | protected function is_type_with_no_id( $type ) { |
| 302 | 315 | return \in_array( $type, [ 'home-page', 'date-archive', 'post-type-archive', 'system-page' ], true ); |
| 303 | 316 | } |
| 304 | 317 | |
| 305 | - // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Exceptions are handled by the catch statement in the method. | |
| 318 | + // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Most exceptions are handled in the method; the unexpected-error catch deliberately re-throws after firing the failure hook. | |
| 306 | 319 | |
| 307 | 320 | /** |
| 308 | 321 | * Rebuilds an Indexable from scratch. |
| 309 | 322 | * |
| 310 | - * @param Indexable $indexable The Indexable to (re)build. | |
| 311 | - * @param array|null $defaults The object type of the Indexable. | |
| 323 | + * @param Indexable $indexable The Indexable to (re)build. | |
| 324 | + * @param array<string, int|string>|null $defaults The object type of the Indexable. | |
| 312 | 325 | * |
| 313 | 326 | * @return Indexable|false The resulting Indexable. |
| 314 | 327 | */ |
| 315 | 328 | public function build( $indexable, $defaults = null ) { |
| @@ -406,9 +419,61 @@ | ||
| 406 | 419 | $indexable = $this->version_manager->set_latest( $indexable ); |
| 407 | 420 | |
| 408 | 421 | return $this->indexable_helper->save_indexable( $indexable, $indexable_before ); |
| 409 | 422 | } catch ( Not_Built_Exception $exception ) { |
| 423 | + $this->logger->debug( | |
| 424 | + $exception->getMessage(), | |
| 425 | + [ | |
| 426 | + 'object_id' => $indexable->object_id, | |
| 427 | + 'object_type' => $indexable->object_type, | |
| 428 | + 'object_sub_type' => $indexable->object_sub_type, | |
| 429 | + 'exception' => \get_class( $exception ), | |
| 430 | + ], | |
| 431 | + ); | |
| 432 | + | |
| 410 | 433 | return false; |
| 434 | + } catch ( Indexing_Failed_Exception $exception ) { | |
| 435 | + // A nested build (e.g. the author indexable built during a post build) already logged the | |
| 436 | + // failure, fired the action and wrapped the original error, so pass it through untouched | |
| 437 | + // to keep the root failing object's identity and avoid reporting the failure twice. | |
| 438 | + throw $exception; | |
| 439 | + } catch ( Throwable $exception ) { | |
| 440 | + $indexing_failed_exception = new Indexing_Failed_Exception( | |
| 441 | + $indexable->object_id, | |
| 442 | + $indexable->object_type, | |
| 443 | + $indexable->object_sub_type, | |
| 444 | + $exception, | |
| 445 | + ); | |
| 446 | + | |
| 447 | + $this->logger->error( | |
| 448 | + $indexing_failed_exception->getMessage(), | |
| 449 | + [ | |
| 450 | + 'object_id' => $indexable->object_id, | |
| 451 | + 'object_type' => $indexable->object_type, | |
| 452 | + 'object_sub_type' => $indexable->object_sub_type, | |
| 453 | + 'exception' => \get_class( $exception ), | |
| 454 | + ], | |
| 455 | + ); | |
| 456 | + | |
| 457 | + /** | |
| 458 | + * Fires when an indexable could not be built because of an unexpected error. | |
| 459 | + * | |
| 460 | + * This action lets third parties observe build failures themselves. | |
| 461 | + * | |
| 462 | + * @param int|null $object_id The object ID of the indexable that failed to build, or null for id-less object types. | |
| 463 | + * @param string $object_type The object type of the indexable that failed to build. | |
| 464 | + * @param string|null $object_sub_type The object sub type of the indexable that failed to build. | |
| 465 | + * @param Throwable $exception The error that caused the failure. | |
| 466 | + */ | |
| 467 | + \do_action( | |
| 468 | + 'wpseo_indexable_indexing_failed', | |
| 469 | + $indexable->object_id, | |
| 470 | + $indexable->object_type, | |
| 471 | + $indexable->object_sub_type, | |
| 472 | + $exception, | |
| 473 | + ); | |
| 474 | + | |
| 475 | + throw $indexing_failed_exception; | |
| 411 | 476 | } |
| 412 | 477 | } |
| 413 | 478 | |
| 414 | 479 | // phpcs:enable |