From 04cc38292c46b9b50eb3e466346a6e982589ca1f Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 15:53:45 +0100 Subject: [PATCH 01/25] Update phpstan.neon.dist to change paths from 'src/' to 'inc/' --- phpstan.neon.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 99f3173..04ebabf 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,4 +4,4 @@ parameters: level: max inferPrivatePropertyTypeFromConstructor: true paths: - - src/ + - inc/ From 55bd2c221abf57fb2f3d8967263a6f2463313f5c Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 15:54:14 +0100 Subject: [PATCH 02/25] Add detailed example functions for post types, post meta, and taxonomies in README --- README.md | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/README.md b/README.md index 7d4f12e..3ee277f 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,240 @@ ``` composer require wearerequired/common-php ``` + +## Example + +```php +/** + * Bootstraps post type integrations. + */ +function bootstrap(): void { + add_action( 'init', __NAMESPACE__ . '\register_post_types' ); + add_action( 'init', __NAMESPACE__ . '\register_post_meta' ); + add_action( 'init', __NAMESPACE__ . '\register_taxonomies' ); + add_action( 'init', __NAMESPACE__ . '\register_taxonomy_for_object_types', 11 ); +} + +/** + * Registers custom post types. + */ +function register_post_types() { + $post_types = new Container(); + + $post_types->add( Profession::NAME, new Profession() ); + $post_types->add( School::NAME, new School() ); + $post_types->add( Office::NAME, new Office() ); + $post_types->add( Event::NAME, new Event() ); + $post_types->add( Apprenticeship::NAME, new Apprenticeship() ); + $post_types->add( TrialApprenticeship::NAME, new TrialApprenticeship() ); + $post_types->add( Organization::NAME, new Organization() ); + + $post_types->bootstrap(); +} + +/** + * Registers post meta. + */ +function register_post_meta() { + $post_meta = new Container(); + + $post_meta->add( PostMeta\ImportID::KEY, new PostMeta\ImportID() ); + $post_meta->add( PostMeta\ImportTimestamp::KEY, new PostMeta\ImportTimestamp() ); + + $post_meta->add( PostMeta\SchoolOfficeID::KEY, new PostMeta\SchoolOfficeID() ); + $post_meta->add( PostMeta\SchoolTelNumber::KEY, new PostMeta\SchoolTelNumber() ); + $post_meta->add( PostMeta\SchoolEmail::KEY, new PostMeta\SchoolEmail() ); + + $post_meta->add( PostMeta\OfficeStreet::KEY, new PostMeta\OfficeStreet() ); + $post_meta->add( PostMeta\OfficePOBox::KEY, new PostMeta\OfficePOBox() ); + $post_meta->add( PostMeta\OfficeEmail::KEY, new PostMeta\OfficeEmail() ); + $post_meta->add( PostMeta\OfficeTelNumber::KEY, new PostMeta\OfficeTelNumber() ); + $post_meta->add( PostMeta\OfficeFax::KEY, new PostMeta\OfficeFax() ); + $post_meta->add( PostMeta\OfficeContactURL::KEY, new PostMeta\OfficeContactURL() ); + $post_meta->add( PostMeta\OfficeOpeningHours::KEY, new PostMeta\OfficeOpeningHours() ); + $post_meta->add( PostMeta\OfficeSendCopyToConsultant::KEY, new PostMeta\OfficeSendCopyToConsultant() ); + + $post_meta->add( PostMeta\EventAdditionalInfo::KEY, new PostMeta\EventAdditionalInfo() ); + $post_meta->add( PostMeta\EventAddress::KEY, new PostMeta\EventAddress() ); + $post_meta->add( PostMeta\EventAuthor::KEY, new PostMeta\EventAuthor() ); + $post_meta->add( PostMeta\EventCity::KEY, new PostMeta\EventCity() ); + $post_meta->add( PostMeta\EventCode::KEY, new PostMeta\EventCode() ); + $post_meta->add( PostMeta\EventCreator::KEY, new PostMeta\EventCreator() ); + $post_meta->add( PostMeta\EventDepartment::KEY, new PostMeta\EventDepartment() ); + $post_meta->add( PostMeta\EventFeeInfoNoFees::KEY, new PostMeta\EventFeeInfoNoFees() ); + $post_meta->add( PostMeta\EventFeeInfoFee::KEY, new PostMeta\EventFeeInfoFee() ); + $post_meta->add( PostMeta\EventFeeInfoCurrency::KEY, new PostMeta\EventFeeInfoCurrency() ); + $post_meta->add( PostMeta\EventFeeInfoDescription::KEY, new PostMeta\EventFeeInfoDescription() ); + $post_meta->add( PostMeta\EventGuideDescr::KEY, new PostMeta\EventGuideDescr() ); + $post_meta->add( PostMeta\EventGuideFirstname::KEY, new PostMeta\EventGuideFirstname() ); + $post_meta->add( PostMeta\EventGuideLastname::KEY, new PostMeta\EventGuideLastname() ); + $post_meta->add( PostMeta\EventHostCity::KEY, new PostMeta\EventHostCity() ); + $post_meta->add( PostMeta\EventHostLastname::KEY, new PostMeta\EventHostLastname() ); + $post_meta->add( PostMeta\EventHostRegion::KEY, new PostMeta\EventHostRegion() ); + $post_meta->add( PostMeta\EventHostType::KEY, new PostMeta\EventHostType() ); + $post_meta->add( PostMeta\EventHostZip::KEY, new PostMeta\EventHostZip() ); + $post_meta->add( PostMeta\EventHostPhone::KEY, new PostMeta\EventHostPhone() ); + $post_meta->add( PostMeta\EventHostMail::KEY, new PostMeta\EventHostMail() ); + $post_meta->add( PostMeta\EventHostWebsite::KEY, new PostMeta\EventHostWebsite() ); + $post_meta->add( PostMeta\EventLocation::KEY, new PostMeta\EventLocation() ); + $post_meta->add( PostMeta\EventOrgDepartment::KEY, new PostMeta\EventOrgDepartment() ); + $post_meta->add( PostMeta\EventRegion::KEY, new PostMeta\EventRegion() ); + $post_meta->add( PostMeta\EventRegistrationLink::KEY, new PostMeta\EventRegistrationLink() ); + $post_meta->add( PostMeta\EventRegistrationMail::KEY, new PostMeta\EventRegistrationMail() ); + $post_meta->add( PostMeta\EventRegistrationName::KEY, new PostMeta\EventRegistrationName() ); + $post_meta->add( PostMeta\EventHasRegistration::KEY, new PostMeta\EventHasRegistration() ); + $post_meta->add( PostMeta\EventRegistrationPhone::KEY, new PostMeta\EventRegistrationPhone() ); + $post_meta->add( PostMeta\EventSeatsMax::KEY, new PostMeta\EventSeatsMax() ); + $post_meta->add( PostMeta\EventSeatsMin::KEY, new PostMeta\EventSeatsMin() ); + $post_meta->add( PostMeta\EventSeatsAvailable::KEY, new PostMeta\EventSeatsAvailable() ); + $post_meta->add( PostMeta\EventSpeakers::KEY, new PostMeta\EventSpeakers() ); + $post_meta->add( PostMeta\EventUnid::KEY, new PostMeta\EventUnid() ); + $post_meta->add( PostMeta\EventZip::KEY, new PostMeta\EventZip() ); + $post_meta->add( PostMeta\EventDateStart::KEY, new PostMeta\EventDateStart() ); + $post_meta->add( PostMeta\EventDateEnd::KEY, new PostMeta\EventDateEnd() ); + $post_meta->add( PostMeta\EventDates::KEY, new PostMeta\EventDates() ); + $post_meta->add( PostMeta\EventIsOnline::KEY, new PostMeta\EventIsOnline() ); + $post_meta->add( PostMeta\EventTargetAudience::KEY, new PostMeta\EventTargetAudience() ); + $post_meta->add( PostMeta\EventSwissdocIDs::KEY, new PostMeta\EventSwissdocIDs() ); + + $post_meta->add( PostMeta\SwissdocNumber::KEY, new PostMeta\SwissdocNumber() ); + + $post_meta->add( PostMeta\Zip::KEY, new PostMeta\Zip() ); + $post_meta->add( PostMeta\City::KEY, new PostMeta\City() ); + $post_meta->add( PostMeta\Lat::KEY, new PostMeta\Lat() ); + $post_meta->add( PostMeta\Lng::KEY, new PostMeta\Lng() ); + + $post_meta->add( PostMeta\ProfessionSNI::KEY, new PostMeta\ProfessionSNI() ); + $post_meta->add( PostMeta\ProfessionCompletedApprenticeshipContracts::KEY, new PostMeta\ProfessionCompletedApprenticeshipContracts() ); + $post_meta->add( PostMeta\ProfessionVideoURLs::KEY, new PostMeta\ProfessionVideoURLs() ); + $post_meta->add( PostMeta\ProfessionPodcastURLs::KEY, new PostMeta\ProfessionPodcastURLs() ); + $post_meta->add( PostMeta\ProfessionSkipImport::KEY, new PostMeta\ProfessionSkipImport() ); + $post_meta->add( PostMeta\ProfessionSchoolRequirementsURL::KEY, new PostMeta\ProfessionSchoolRequirementsURL() ); + $post_meta->add( PostMeta\ProfessionHint::KEY, new PostMeta\ProfessionHint() ); + $post_meta->add( PostMeta\ProfessionForeignLanguage::KEY, new PostMeta\ProfessionForeignLanguage() ); + + $post_meta->add( PostMeta\OrganizationLogoURL::KEY, new PostMeta\OrganizationLogoURL() ); + + $post_meta->bootstrap(); +} + +/** + * Registers custom taxonomies. + */ +function register_taxonomies(): void { + $taxonomies = new Container(); + + $taxonomies->add( ProfessionField::NAME, new ProfessionField() ); + $taxonomies->add( ProfessionInterest::NAME, new ProfessionInterest() ); + $taxonomies->add( ProfessionSector::NAME, new ProfessionSector() ); + $taxonomies->add( ProfessionAlternativeTitle::NAME, new ProfessionAlternativeTitle() ); + $taxonomies->add( ProfessionDuration::NAME, new ProfessionDuration() ); + $taxonomies->add( ProfessionQualification::NAME, new ProfessionQualification() ); + $taxonomies->add( ProfessionBilingualEducation::NAME, new ProfessionBilingualEducation() ); + $taxonomies->add( EventSpeaker::NAME, new EventSpeaker() ); + $taxonomies->add( EventAudience::NAME, new EventAudience() ); + $taxonomies->add( EventTopic::NAME, new EventTopic() ); + $taxonomies->add( EventStatus::NAME, new EventStatus() ); + $taxonomies->add( Swissdoc::NAME, new Swissdoc() ); + $taxonomies->add( ApprenticeshipOrganization::NAME, new ApprenticeshipOrganization() ); + $taxonomies->add( UserNationality::NAME, new UserNationality() ); + $taxonomies->add( UserLanguage::NAME, new UserLanguage() ); + $taxonomies->add( ApprenticeshipYear::NAME, new ApprenticeshipYear() ); + $taxonomies->add( LinkDistrictOrganizationApprenticeship::NAME, new LinkDistrictOrganizationApprenticeship() ); + $taxonomies->add( LinkDistrictOrganizationTrialApprenticeship::NAME, new LinkDistrictOrganizationTrialApprenticeship() ); + $taxonomies->add( LinkSwissdocOrganizationApprenticeship::NAME, new LinkSwissdocOrganizationApprenticeship() ); + $taxonomies->add( LinkSwissdocOrganizationTrialApprenticeship::NAME, new LinkSwissdocOrganizationTrialApprenticeship() ); + $taxonomies->add( OrganizationEmail::NAME, new OrganizationEmail() ); + + $district = new District(); + $taxonomies->add( District::NAME, $district ); + + $meta_position = new TermMeta\Position(); + $meta_position->register(); + add_action( + 'admin_init', + static function () use ( $district, $meta_position ): void { + $meta_position_ui = new Admin\PositionTermUI( + $district, + $meta_position + ); + $meta_position_ui->register(); + } + ); + + $meta_zip = new TermMeta\Zip(); + $meta_zip->register(); + add_action( + 'admin_init', + static function () use ( $district, $meta_zip ): void { + $meta_zip_ui = new Admin\ZipTermUI( + $district, + $meta_zip + ); + $meta_zip_ui->register(); + } + ); + + $meta_city = new TermMeta\City(); + $meta_city->register(); + add_action( + 'admin_init', + static function () use ( $district, $meta_city ): void { + $meta_city_ui = new Admin\CityTermUI( + $district, + $meta_city + ); + $meta_city_ui->register(); + } + ); + + $taxonomies->bootstrap(); +} + +/** + * Registers taxonomies for object types. + */ +function register_taxonomy_for_object_types(): void { + register_taxonomy_for_object_type( ProfessionField::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionField::NAME, PostType\Event::NAME ); + + register_taxonomy_for_object_type( ProfessionInterest::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionSector::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionAlternativeTitle::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionDuration::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionQualification::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( ProfessionBilingualEducation::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( EventSpeaker::NAME, PostType\Event::NAME ); + register_taxonomy_for_object_type( EventAudience::NAME, PostType\Event::NAME ); + register_taxonomy_for_object_type( EventTopic::NAME, PostType\Event::NAME ); + register_taxonomy_for_object_type( EventStatus::NAME, PostType\Event::NAME ); + + register_taxonomy_for_object_type( District::NAME, PostType\School::NAME ); + register_taxonomy_for_object_type( District::NAME, PostType\Office::NAME ); + register_taxonomy_for_object_type( District::NAME, PostType\Apprenticeship::NAME ); + register_taxonomy_for_object_type( District::NAME, PostType\TrialApprenticeship::NAME ); + register_taxonomy_for_object_type( District::NAME, PostType\Event::NAME ); + register_taxonomy_for_object_type( District::NAME, PostType\Organization::NAME ); + + register_taxonomy_for_object_type( Swissdoc::NAME, PostType\Profession::NAME ); + register_taxonomy_for_object_type( Swissdoc::NAME, PostType\Apprenticeship::NAME ); + register_taxonomy_for_object_type( Swissdoc::NAME, PostType\TrialApprenticeship::NAME ); + + register_taxonomy_for_object_type( ApprenticeshipOrganization::NAME, PostType\Apprenticeship::NAME ); + register_taxonomy_for_object_type( ApprenticeshipOrganization::NAME, PostType\TrialApprenticeship::NAME ); + register_taxonomy_for_object_type( ApprenticeshipOrganization::NAME, PostType\Organization::NAME ); + register_taxonomy_for_object_type( ApprenticeshipOrganization::NAME, PostType\Event::NAME ); + + register_taxonomy_for_object_type( LinkDistrictOrganizationApprenticeship::NAME, PostType\Organization::NAME ); + register_taxonomy_for_object_type( LinkDistrictOrganizationTrialApprenticeship::NAME, PostType\Organization::NAME ); + register_taxonomy_for_object_type( LinkSwissdocOrganizationApprenticeship::NAME, PostType\Organization::NAME ); + register_taxonomy_for_object_type( LinkSwissdocOrganizationTrialApprenticeship::NAME, PostType\Organization::NAME ); + + register_taxonomy_for_object_type( OrganizationEmail::NAME, PostType\Organization::NAME ); + + register_taxonomy_for_object_type( ApprenticeshipYear::NAME, PostType\Apprenticeship::NAME ); + + register_taxonomy_for_object_type( UserNationality::NAME, 'user' ); + register_taxonomy_for_object_type( UserLanguage::NAME, 'user' ); +} +``` From 57872bba3798f6658dfda8802bbeb74b718f2499 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 15:54:22 +0100 Subject: [PATCH 03/25] Increase memory limit for PHPStan analysis script in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 41a716e..6d4e165 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,6 @@ } }, "scripts": { - "analyze": "vendor/bin/phpstan analyze --no-progress" + "analyze": "vendor/bin/phpstan analyze --no-progress --memory-limit 1G" } } From c90e6456cf04554ec5e151a3629ed777d0f03a81 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 15:58:22 +0100 Subject: [PATCH 04/25] Remove deprecated Legacy implementations --- inc/Legacy/PostMeta.php | 167 ---------------------------------------- inc/Legacy/PostType.php | 51 ------------ inc/Legacy/Taxonomy.php | 83 -------------------- inc/Legacy/TermMeta.php | 167 ---------------------------------------- 4 files changed, 468 deletions(-) delete mode 100644 inc/Legacy/PostMeta.php delete mode 100644 inc/Legacy/PostType.php delete mode 100644 inc/Legacy/Taxonomy.php delete mode 100644 inc/Legacy/TermMeta.php diff --git a/inc/Legacy/PostMeta.php b/inc/Legacy/PostMeta.php deleted file mode 100644 index b5727ad..0000000 --- a/inc/Legacy/PostMeta.php +++ /dev/null @@ -1,167 +0,0 @@ -post_type(), - static::KEY, - $this->get_args() - ); - } - - /** - * Gets post meta arguments for this post meta object. - * - * @since 0.1.0 - * - * @see register_meta() For the supported arguments. - * - * @return array Post meta arguments. - */ - protected function get_args(): array { - return [ - 'type' => $this->type(), - 'description' => $this->description(), - 'single' => $this->is_single(), - 'default' => $this->default(), - 'sanitize_callback' => [ $this, 'sanitize' ], - 'auth_callback' => [ $this, 'auth' ], - 'show_in_rest' => $this->show_in_rest(), - ]; - } - - /** - * The post type to register a meta key for. - * - * Pass an empty string to register the meta key across all existing post types. - * - * @since 0.1.0 - * - * @return string Post type to register a meta key for. - */ - protected function post_type(): string { - return ''; - } - - /** - * The type of data associated with this meta key. - * - * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. - * - * @since 0.1.0 - * - * @return string Type of the data. - */ - protected function type(): string { - return 'string'; - } - - /** - * A description of the data attached to this meta key. - * - * @since 0.1.0 - * - * @return string Description of the data. - */ - protected function description(): string { - return ''; - } - - /** - * Whether the meta key has one value per object, or an array of values per object. - * - * @since 0.1.0 - * - * @return bool Whether the meta key has one value per object. - */ - protected function is_single(): bool { - return false; - } - - /** - * The default value if no value has been set yet. - * - * @since 0.1.0 - * - * @return mixed The default value. - */ - protected function default() { - return ''; - } - - /** - * Sanitization of the post meta data. - * - * @since 0.1.0 - * - * @param mixed $meta_value Meta value to sanitize. - * @param string $meta_key Meta key. - * @param string $object_type Object type. - * @return mixed Sanitized meta value. - */ - public function sanitize( $meta_value, $meta_key, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return $meta_value; - } - - /** - * Whether the user is allowed to edit meta. - * - * @since 0.1.0 - * - * @param bool $allowed Whether the user can add the post meta. Default false. - * @param string $meta_key The meta key. - * @param int $object_id Object ID. - * @param int $user_id User ID. - * @param string $cap Capability name. - * @param array $caps User capabilities. - * @return bool False if the key is protected, true otherwise. - */ - public function auth( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return ! is_protected_meta( $meta_key, 'post' ); - } - - /** - * Whether data associated with this meta key can be considered public and - * should be accessible via the REST API. - * - * When registering complex meta values this argument may optionally be an - * array with 'schema' or 'prepare_callback' keys instead of a boolean. - * - * @since 0.1.0 - * - * @return bool|array Whether the data is public. - */ - protected function show_in_rest() { - return false; - } -} diff --git a/inc/Legacy/PostType.php b/inc/Legacy/PostType.php deleted file mode 100644 index 0a13eab..0000000 --- a/inc/Legacy/PostType.php +++ /dev/null @@ -1,51 +0,0 @@ -get_args() - ); - return ! is_wp_error( $post_type ); - } - - return true; - } - - /** - * Gets post type arguments for this post type object. - * - * @since 0.1.0 - * - * @return array Post type arguments. - */ - abstract protected function get_args(): array; -} diff --git a/inc/Legacy/Taxonomy.php b/inc/Legacy/Taxonomy.php deleted file mode 100644 index e5601aa..0000000 --- a/inc/Legacy/Taxonomy.php +++ /dev/null @@ -1,83 +0,0 @@ -get_object_types(), - $this->get_args() - ); - return ! is_wp_error( $taxonomy ); - } - - return true; - } - - /** - * Sets object types the taxonomy is associated with. - * - * @since 0.1.0 - * - * @param array|string $object_type Object type or array of object types with which the taxonomy should be associated. - */ - public function set_object_types( $object_type ) { - $this->object_types = (array) $object_type; - } - - /** - * Gets object types the taxonomy is associated with. - * - * @since 0.1.0 - * - * @return array Object types the taxonomy is associated with. - */ - public function get_object_types() { - return $this->object_types; - } - - /** - * Gets taxonomy arguments for this taxonomy object. - * - * @since 0.1.0 - * - * @return array Taxonomy arguments. - */ - abstract protected function get_args(); -} diff --git a/inc/Legacy/TermMeta.php b/inc/Legacy/TermMeta.php deleted file mode 100644 index d9021e8..0000000 --- a/inc/Legacy/TermMeta.php +++ /dev/null @@ -1,167 +0,0 @@ -taxonomy(), - static::KEY, - $this->get_args() - ); - } - - /** - * Gets term meta arguments for this term meta object. - * - * @since 0.1.0 - * - * @see register_meta() For the supported arguments. - * - * @return array Term meta arguments. - */ - protected function get_args(): array { - return [ - 'type' => $this->type(), - 'description' => $this->description(), - 'single' => $this->is_single(), - 'default' => $this->default(), - 'sanitize_callback' => [ $this, 'sanitize' ], - 'auth_callback' => [ $this, 'auth' ], - 'show_in_rest' => $this->show_in_rest(), - ]; - } - - /** - * The taxonomy to register a meta key for. - * - * Pass an empty string to register the meta key across all existing taxonomies. - * - * @since 0.1.0 - * - * @return string Taxonomy to register a meta key for. - */ - protected function taxonomy(): string { - return ''; - } - - /** - * The type of data associated with this meta key. - * - * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. - * - * @since 0.1.0 - * - * @return string Type of the data. - */ - protected function type(): string { - return 'string'; - } - - /** - * A description of the data attached to this meta key. - * - * @since 0.1.0 - * - * @return string Description of the data. - */ - protected function description(): string { - return ''; - } - - /** - * Whether the meta key has one value per object, or an array of values per object. - * - * @since 0.1.0 - * - * @return bool Whether the meta key has one value per object. - */ - protected function is_single(): bool { - return false; - } - - /** - * The default value if no value has been set yet. - * - * @since 0.1.0 - * - * @return mixed The default value. - */ - protected function default() { - return ''; - } - - /** - * Sanitization of the term meta data. - * - * @since 0.1.0 - * - * @param mixed $meta_value Meta value to sanitize. - * @param string $meta_key Meta key. - * @param string $object_type Object type. - * @return mixed Sanitized meta value. - */ - public function sanitize( $meta_value, $meta_key, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return $meta_value; - } - - /** - * Whether the user is allowed to edit meta. - * - * @since 0.1.0 - * - * @param bool $allowed Whether the user can add the term meta. Default false. - * @param string $meta_key The meta key. - * @param int $object_id Object ID. - * @param int $user_id User ID. - * @param string $cap Capability name. - * @param array $caps User capabilities. - * @return bool False if the key is protected, true otherwise. - */ - public function auth( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return ! is_protected_meta( $meta_key, 'term' ); - } - - /** - * Whether data associated with this meta key can be considered public and - * should be accessible via the REST API. - * - * When registering complex meta values this argument may optionally be an - * array with 'schema' or 'prepare_callback' keys instead of a boolean. - * - * @since 0.1.0 - * - * @return bool|array Whether the data is public. - */ - protected function show_in_rest() { - return false; - } -} From 600c49ba2df6e1d4a356ea2dcd2c07b98121582e Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:05:07 +0100 Subject: [PATCH 05/25] Add strict_types declarations --- inc/Admin/AjaxAction.php | 2 ++ inc/Admin/Page.php | 2 ++ inc/Admin/PageView.php | 2 ++ inc/Admin/PostAction.php | 2 ++ inc/Container.php | 2 ++ inc/Contracts/Registrable.php | 2 ++ inc/Contracts/ShortcodeView.php | 2 ++ inc/Shortcode.php | 2 ++ inc/TermMetaAdminUI.php | 2 ++ 9 files changed, 18 insertions(+) diff --git a/inc/Admin/AjaxAction.php b/inc/Admin/AjaxAction.php index d5671e4..c3b2699 100644 --- a/inc/Admin/AjaxAction.php +++ b/inc/Admin/AjaxAction.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Admin; use Required\Common\Contracts\Registrable; diff --git a/inc/Admin/Page.php b/inc/Admin/Page.php index a4ebb22..1ea9cc7 100644 --- a/inc/Admin/Page.php +++ b/inc/Admin/Page.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Admin; use Required\Common\Contracts\Registrable; diff --git a/inc/Admin/PageView.php b/inc/Admin/PageView.php index 2923d8f..f2e2d40 100644 --- a/inc/Admin/PageView.php +++ b/inc/Admin/PageView.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Admin; /** diff --git a/inc/Admin/PostAction.php b/inc/Admin/PostAction.php index 15b5c42..e848e0d 100644 --- a/inc/Admin/PostAction.php +++ b/inc/Admin/PostAction.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Admin; use Required\Common\Contracts\Registrable; diff --git a/inc/Container.php b/inc/Container.php index 6936e5e..de669a8 100644 --- a/inc/Container.php +++ b/inc/Container.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common; use Required\Common\Contracts\Registrable; diff --git a/inc/Contracts/Registrable.php b/inc/Contracts/Registrable.php index 9366a4b..0a8709d 100644 --- a/inc/Contracts/Registrable.php +++ b/inc/Contracts/Registrable.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Contracts; /** diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php index c7c2e02..fc3a138 100644 --- a/inc/Contracts/ShortcodeView.php +++ b/inc/Contracts/ShortcodeView.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common\Contracts; /** diff --git a/inc/Shortcode.php b/inc/Shortcode.php index bb2a6f8..37ef998 100644 --- a/inc/Shortcode.php +++ b/inc/Shortcode.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common; use Required\Common\Contracts\Registrable; diff --git a/inc/TermMetaAdminUI.php b/inc/TermMetaAdminUI.php index e3bd628..47ee23b 100644 --- a/inc/TermMetaAdminUI.php +++ b/inc/TermMetaAdminUI.php @@ -5,6 +5,8 @@ * @since 0.1.0 */ +declare( strict_types=1 ); + namespace Required\Common; use Required\Common\Contracts\Registrable; From 21cab2027f032c49c1cbd99a9240ba8926429cdc Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:06:35 +0100 Subject: [PATCH 06/25] Type Admin helpers --- inc/Admin/AjaxAction.php | 6 ++--- inc/Admin/Page.php | 50 ++++++++++++++++++++-------------------- inc/Admin/PageView.php | 2 +- inc/Admin/PostAction.php | 6 ++--- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/inc/Admin/AjaxAction.php b/inc/Admin/AjaxAction.php index c3b2699..5ae0b51 100644 --- a/inc/Admin/AjaxAction.php +++ b/inc/Admin/AjaxAction.php @@ -18,7 +18,7 @@ */ abstract class AjaxAction implements Registrable { - public const ACTION = 'ajax-action'; + public const string ACTION = 'ajax-action'; /** * Whether the Ajax action can be called unauthenticated. @@ -34,7 +34,7 @@ abstract class AjaxAction implements Registrable { * * @param bool $allow Whether the Ajax action can be called unauthenticated. */ - public function allow_unauthenticated( bool $allow ) { + public function allow_unauthenticated( bool $allow ): void { $this->allow_unauthenticated = $allow; } @@ -43,7 +43,7 @@ public function allow_unauthenticated( bool $allow ) { * * @since 0.1.0 */ - abstract public function callback(); + abstract public function callback(): void; /** * Registers the action. diff --git a/inc/Admin/Page.php b/inc/Admin/Page.php index 1ea9cc7..448f78f 100644 --- a/inc/Admin/Page.php +++ b/inc/Admin/Page.php @@ -27,7 +27,7 @@ class Page implements Registrable { * * @var int */ - public const ADMIN = 1; + public const int ADMIN = 1; /** * Type of admin page. @@ -38,7 +38,7 @@ class Page implements Registrable { * * @var int */ - public const ADMIN_NETWORK = 2; + public const int ADMIN_NETWORK = 2; /** * Type of admin page. @@ -49,7 +49,7 @@ class Page implements Registrable { * * @var int */ - public const ADMIN_USER = 3; + public const int ADMIN_USER = 3; /** * Type of admin screen. @@ -58,7 +58,7 @@ class Page implements Registrable { * * @var int */ - private $admin; + private int $admin; /** * The text to be displayed in the title tags of the page when the menu is selected. @@ -67,7 +67,7 @@ class Page implements Registrable { * * @var string */ - private $title; + private string $title; /** * The text to be used for the menu. @@ -76,7 +76,7 @@ class Page implements Registrable { * * @var string */ - private $menu_title; + private string $menu_title; /** * The capability required for this menu to be displayed to the user. @@ -85,7 +85,7 @@ class Page implements Registrable { * * @var string */ - private $capability; + private string $capability; /** * The slug name to refer to this menu by (should be unique for this menu). @@ -94,7 +94,7 @@ class Page implements Registrable { * * @var string */ - private $menu_slug; + private string $menu_slug; /** * The view to render the admin page. @@ -103,7 +103,7 @@ class Page implements Registrable { * * @var \Required\Common\Admin\PageView */ - private $view; + private PageView $view; /** * The slug name for the parent menu (or the file name of a standard WordPress admin page). @@ -112,7 +112,7 @@ class Page implements Registrable { * * @var string */ - private $parent_slug; + private string $parent_slug; /** * The icon to be used for the menu. @@ -121,7 +121,7 @@ class Page implements Registrable { * * @var string */ - private $icon; + private string $icon; /** * The position in the menu order this one should appear. @@ -130,7 +130,7 @@ class Page implements Registrable { * * @var null|int */ - private $position; + private ?int $position; /** * The page's hook_suffix. @@ -139,16 +139,16 @@ class Page implements Registrable { * * @var string */ - private $hook_name; + private string|false $hook_name = false; /** * Callback to be called when page is loaded. * * @since 0.1.0 * - * @var string + * @var callable|null */ - private $on_load_callback; + private $on_load_callback = null; /** * Constructor. @@ -165,7 +165,7 @@ class Page implements Registrable { * @param string $icon Optional. The icon to be used for the menu. * @param null|int $position Optional. The position in the menu order this one should appear. */ - public function __construct( int $admin, string $title, string $menu_title, string $capability, string $menu_slug, PageView $view, string $parent_slug = '', string $icon = '', $position = null ) { + public function __construct( int $admin, string $title, string $menu_title, string $capability, string $menu_slug, PageView $view, string $parent_slug = '', string $icon = '', ?int $position = null ) { $this->admin = $admin; $this->title = $title; $this->menu_title = $menu_title; @@ -184,7 +184,7 @@ public function __construct( int $admin, string $title, string $menu_title, stri * * @return string */ - private function get_action() { + private function get_action(): string { switch ( $this->admin ) { case static::ADMIN: return 'admin_menu'; @@ -207,9 +207,9 @@ private function get_action() { * * @return callable Callback for adding the page to the admin menu. */ - private function get_callback() { + private function get_callback(): \Closure { if ( $this->parent_slug ) { - return function() { + return function (): void { $this->hook_name = add_submenu_page( $this->parent_slug, $this->title, @@ -219,7 +219,7 @@ private function get_callback() { [ $this->view, 'render' ] ); - if ( $this->hook_name && is_callable( $this->on_load_callback ) ) { + if ( $this->hook_name && \is_callable( $this->on_load_callback ) ) { add_action( "load-{$this->hook_name}", $this->on_load_callback @@ -227,7 +227,7 @@ private function get_callback() { } }; } else { - return function() { + return function (): void { $this->hook_name = add_menu_page( $this->title, $this->menu_title, @@ -238,7 +238,7 @@ private function get_callback() { $this->position ); - if ( $this->hook_name && is_callable( $this->on_load_callback ) ) { + if ( $this->hook_name && \is_callable( $this->on_load_callback ) ) { add_action( "load-{$this->hook_name}", $this->on_load_callback @@ -255,7 +255,7 @@ private function get_callback() { * * @return string The hook name of the menu page. */ - public function get_hook_name() { + public function get_hook_name(): string|false { return $this->hook_name; } @@ -266,7 +266,7 @@ public function get_hook_name() { * * @return \Required\Common\Admin\PageView The view of the page. */ - public function get_view() { + public function get_view(): PageView { return $this->view; } @@ -277,7 +277,7 @@ public function get_view() { * * @param callable $callback Callback called when page is loaded. */ - public function on_load( callable $callback ) { + public function on_load( callable $callback ): void { $this->on_load_callback = $callback; } diff --git a/inc/Admin/PageView.php b/inc/Admin/PageView.php index f2e2d40..99323e5 100644 --- a/inc/Admin/PageView.php +++ b/inc/Admin/PageView.php @@ -21,5 +21,5 @@ interface PageView { * * @since 0.1.0 */ - public function render(); + public function render(): void; } diff --git a/inc/Admin/PostAction.php b/inc/Admin/PostAction.php index e848e0d..0b4c166 100644 --- a/inc/Admin/PostAction.php +++ b/inc/Admin/PostAction.php @@ -18,14 +18,14 @@ */ abstract class PostAction implements Registrable { - public const ACTION = 'post-action'; + public const string ACTION = 'post-action'; /** * The callback of the action. * * @since 0.1.0 */ - abstract public function callback(); + abstract public function callback(): void; /** * Registers the action. @@ -45,7 +45,7 @@ public function register(): bool { * * @return string URL to admin-post.php. */ - public static function url() { + public static function url(): string { return admin_url( 'admin-post.php?action=' . static::ACTION ); } } From 9794843e880add67f8b15401bff2dec372ef5458 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:07:18 +0100 Subject: [PATCH 07/25] Type Container registry --- inc/Container.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inc/Container.php b/inc/Container.php index de669a8..a3761e5 100644 --- a/inc/Container.php +++ b/inc/Container.php @@ -24,9 +24,9 @@ class Container { * * @since 0.1.0 * - * @var \Required\Common\Contracts\Registrable[] + * @var array */ - private $values = []; + private array $values = []; /** * Whether this container is $bootstrapped. @@ -35,18 +35,18 @@ class Container { * * @var bool */ - private $bootstrapped = false; + private bool $bootstrapped = false; /** * Adds a new registrable object to the container. * * @since 0.1.0 * - * @param string $name Name of the registrable object. - * @param \Required\Common\Contracts\Registrable $callable Registrable object. + * @param string $name Name of the registrable object. + * @param \Required\Common\Contracts\Registrable $registrable Registrable object. */ - public function add( $name, Registrable $callable ) { - $this->values[ $name ] = $callable; + public function add( string $name, Registrable $registrable ): void { + $this->values[ $name ] = $registrable; } /** From ac448b5d061edbeb9c03913edeb959e3417f9984 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:07:44 +0100 Subject: [PATCH 08/25] Fix Page get_callback phpdoc --- inc/Admin/Page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Admin/Page.php b/inc/Admin/Page.php index 448f78f..a3eea79 100644 --- a/inc/Admin/Page.php +++ b/inc/Admin/Page.php @@ -205,7 +205,7 @@ private function get_action(): string { * * @since 0.1.0 * - * @return callable Callback for adding the page to the admin menu. + * @return \Closure Callback for adding the page to the admin menu. */ private function get_callback(): \Closure { if ( $this->parent_slug ) { From 884f8e365af4355b179138042b2e9ac04eed6851 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:09:03 +0100 Subject: [PATCH 09/25] Type shortcode view contract --- inc/Contracts/ShortcodeView.php | 8 ++++---- inc/Shortcode.php | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php index fc3a138..72865fa 100644 --- a/inc/Contracts/ShortcodeView.php +++ b/inc/Contracts/ShortcodeView.php @@ -21,10 +21,10 @@ interface ShortcodeView { * * @since 0.1.0 * - * @param array $attributes Attributes of the shortcode. - * @param null $content The content. - * @param string $shortcode_tag The shortcode tag. + * @param array $attributes Attributes of the shortcode. + * @param string|null $content The content. + * @param string $shortcode_tag The shortcode tag. * @return string HTML output of the shortcode. */ - public function render( $attributes, $content, $shortcode_tag ): string; + public function render( array $attributes, ?string $content, string $shortcode_tag ): string; } diff --git a/inc/Shortcode.php b/inc/Shortcode.php index 37ef998..897d42e 100644 --- a/inc/Shortcode.php +++ b/inc/Shortcode.php @@ -10,6 +10,7 @@ namespace Required\Common; use Required\Common\Contracts\Registrable; +use Required\Common\Contracts\ShortcodeView; /** * Class used to implement shortcode. @@ -18,7 +19,7 @@ */ abstract class Shortcode implements Registrable { - public const TAG = 'shortcode'; + public const string TAG = 'shortcode'; /** * Shortcode view object. @@ -27,7 +28,7 @@ abstract class Shortcode implements Registrable { * * @var \Required\Common\Contracts\ShortcodeView */ - protected $view; + protected ShortcodeView $view; /** * Creates a shortcode object. @@ -36,7 +37,7 @@ abstract class Shortcode implements Registrable { * * @param \Required\Common\Contracts\ShortcodeView $view Shortcode View. */ - public function __construct( $view ) { + public function __construct( ShortcodeView $view ) { $this->view = $view; } @@ -48,6 +49,10 @@ public function __construct( $view ) { * @return bool Whether shortcode was registered successfully. */ public function register(): bool { + if ( '' === static::TAG ) { + return false; + } + add_shortcode( static::TAG, [ $this->view, 'render' ] From 7646d3520909745296c595642ef9c5aca8c0e9cc Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:09:30 +0100 Subject: [PATCH 10/25] Align ShortcodeView attribute types --- inc/Contracts/ShortcodeView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php index 72865fa..e00952f 100644 --- a/inc/Contracts/ShortcodeView.php +++ b/inc/Contracts/ShortcodeView.php @@ -21,7 +21,7 @@ interface ShortcodeView { * * @since 0.1.0 * - * @param array $attributes Attributes of the shortcode. + * @param array $attributes Attributes of the shortcode. * @param string|null $content The content. * @param string $shortcode_tag The shortcode tag. * @return string HTML output of the shortcode. From 91179c895a49a65aebddeb2f2b6479113c1f4c57 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:09:51 +0100 Subject: [PATCH 11/25] Widen ShortcodeView attribute keys --- inc/Contracts/ShortcodeView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php index e00952f..8f8196b 100644 --- a/inc/Contracts/ShortcodeView.php +++ b/inc/Contracts/ShortcodeView.php @@ -21,7 +21,7 @@ interface ShortcodeView { * * @since 0.1.0 * - * @param array $attributes Attributes of the shortcode. + * @param array $attributes Attributes of the shortcode. * @param string|null $content The content. * @param string $shortcode_tag The shortcode tag. * @return string HTML output of the shortcode. From 8de801c2c336f78885970db1cd98208d3773fb3f Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:20:38 +0100 Subject: [PATCH 12/25] Type PostType NAME and args --- inc/PostType.php | 55 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/inc/PostType.php b/inc/PostType.php index 1860fb6..e473519 100644 --- a/inc/PostType.php +++ b/inc/PostType.php @@ -20,7 +20,7 @@ */ abstract class PostType implements Registrable, PostTypeInterface { - public const NAME = 'post'; + public const string NAME = 'post'; /** * Creates a post type object. @@ -30,10 +30,57 @@ abstract class PostType implements Registrable, PostTypeInterface { * @return bool Whether the post type was registered successfully. */ public function register(): bool { - if ( ! post_type_exists( static::NAME ) ) { + /** @var lowercase-string&non-empty-string $name */ + $name = static::NAME; + + if ( ! post_type_exists( $name ) ) { + /** + * WordPress stubs currently treat this as an array shape. `johnbillion/args` + * returns a plain `array`, so we assert the expected shape here. + * + * @phpstan-var array{ + * label?: string, + * labels?: string[], + * description?: string, + * public?: bool, + * hierarchical?: bool, + * exclude_from_search?: bool, + * publicly_queryable?: bool, + * show_ui?: bool, + * show_in_menu?: bool|string, + * show_in_nav_menus?: bool, + * show_in_admin_bar?: bool, + * show_in_rest?: bool, + * rest_base?: string, + * rest_namespace?: string, + * rest_controller_class?: string, + * autosave_rest_controller_class?: string|bool, + * revisions_rest_controller_class?: string|bool, + * late_route_registration?: bool, + * menu_position?: int, + * menu_icon?: string, + * capability_type?: string|array, + * capabilities?: string[], + * map_meta_cap?: bool, + * supports?: array|false, + * register_meta_box_cb?: callable, + * taxonomies?: string[], + * has_archive?: bool|string, + * rewrite?: bool|array{slug?: string, with_front?: bool, feeds?: bool, pages?: bool, ep_mask?: int}, + * query_var?: string|bool, + * can_export?: bool, + * delete_with_user?: bool, + * template?: array, + * template_lock?: string|false, + * _builtin?: bool, + * _edit_link?: string, + * } $args + */ + $args = $this->get_args()->toArray(); + $post_type = register_post_type( - static::NAME, - $this->get_args()->toArray() + $name, + $args ); return ! is_wp_error( $post_type ); } From 0d31acccd94ec7403bdfc0c6d70335de25cf3ccc Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:20:41 +0100 Subject: [PATCH 13/25] Type Taxonomy object types --- inc/Taxonomy.php | 58 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/inc/Taxonomy.php b/inc/Taxonomy.php index 077ddfe..689b176 100644 --- a/inc/Taxonomy.php +++ b/inc/Taxonomy.php @@ -20,16 +20,10 @@ */ abstract class Taxonomy implements Registrable, TaxonomyInterface { - public const NAME = 'category'; + public const string NAME = 'category'; - /** - * Object types the taxonomy is associated with. - * - * @since 0.1.0 - * - * @var array - */ - protected $object_types = []; + /** @var array */ + protected array $object_types = []; /** * Creates a taxonomy object. @@ -39,11 +33,47 @@ abstract class Taxonomy implements Registrable, TaxonomyInterface { * @return bool Whether the taxonomy was registered successfully. */ public function register(): bool { - if ( ! taxonomy_exists( static::NAME ) ) { + $name = static::NAME; + + if ( ! taxonomy_exists( $name ) ) { + /** + * WordPress stubs currently treat this as an array shape. `johnbillion/args` + * returns a plain `array`, so we assert the expected shape here. + * + * @phpstan-var array{ + * labels?: string[], + * description?: string, + * public?: bool, + * publicly_queryable?: bool, + * hierarchical?: bool, + * show_ui?: bool, + * show_in_menu?: bool, + * show_in_nav_menus?: bool, + * show_in_rest?: bool, + * rest_base?: string, + * rest_namespace?: string, + * rest_controller_class?: string, + * show_tagcloud?: bool, + * show_in_quick_edit?: bool, + * show_admin_column?: bool, + * meta_box_cb?: bool|callable, + * meta_box_sanitize_cb?: callable, + * capabilities?: array{manage_terms?: string, edit_terms?: string, delete_terms?: string, assign_terms?: string}, + * rewrite?: bool|array{slug?: string, with_front?: bool, hierarchical?: bool, ep_mask?: int}, + * query_var?: string|bool, + * update_count_callback?: callable, + * default_term?: string|array{name?: string, slug?: string, description?: string}, + * sort?: bool, + * args?: array, + * _builtin?: bool, + * } $args + */ + $args = $this->get_args()->toArray(); + $taxonomy = register_taxonomy( - static::NAME, + $name, $this->get_object_types(), - $this->get_args()->toArray() + $args ); return ! is_wp_error( $taxonomy ); } @@ -58,8 +88,8 @@ public function register(): bool { * * @param string[]|string $object_type Object type or array of object types with which the taxonomy should be associated. */ - public function set_object_types( $object_type ): void { - $this->object_types = (array) $object_type; + public function set_object_types( array|string $object_type ): void { + $this->object_types = array_map( 'strval', (array) $object_type ); } /** From 06f2cf4156a56ca40c63e1b4d64e0a7c4ac88fe1 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:20:44 +0100 Subject: [PATCH 14/25] Fix meta registration typings --- inc/PostMeta.php | 52 ++++++++++++++++++++++++++++++++++++++++-------- inc/TermMeta.php | 52 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/inc/PostMeta.php b/inc/PostMeta.php index 25ae138..4061a4d 100644 --- a/inc/PostMeta.php +++ b/inc/PostMeta.php @@ -23,7 +23,7 @@ abstract class PostMeta implements Registrable, PostMetaInterface { /** * Post meta key. */ - public const KEY = null; + public const string KEY = ''; /** * Registers the object. @@ -33,10 +33,30 @@ abstract class PostMeta implements Registrable, PostMetaInterface { * @return bool Whether the post meta was registered successfully. */ public function register(): bool { + if ( static::KEY === '' ) { + return false; + } + + /** + * @phpstan-var array{ + * object_subtype?: string, + * type?: string, + * label?: string, + * description?: string, + * single?: bool, + * default?: mixed, + * sanitize_callback?: callable, + * auth_callback?: callable, + * show_in_rest?: bool|array, + * revisions_enabled?: bool, + * } $args + */ + $args = $this->get_args()->toArray(); + return register_post_meta( $this->post_type(), static::KEY, - $this->get_args()->toArray() + $args ); } @@ -57,7 +77,7 @@ protected function get_args(): PostMetaArgs { $args->single = $this->is_single(); $args->default = $this->default(); $args->sanitize_callback = [ $this, 'sanitize' ]; - $args->auth_callback = [ $this, 'auth' ]; + $args->auth_callback = [ $this, 'auth_callback' ]; $args->show_in_rest = $this->show_in_rest(); return $args; @@ -85,6 +105,7 @@ protected function post_type(): string { * * @return string Type of the data. */ + /** @return 'array'|'boolean'|'integer'|'number'|'object'|'string' */ protected function type(): string { return PostMetaArgs::TYPE_STRING; } @@ -118,7 +139,7 @@ protected function is_single(): bool { * * @return mixed The default value. */ - protected function default() { + protected function default(): mixed { return ''; } @@ -132,7 +153,7 @@ protected function default() { * @param string $object_type Object type. * @return mixed Sanitized meta value. */ - public function sanitize( $meta_value, string $meta_key, string $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public function sanitize( mixed $meta_value, string $meta_key, string $object_type ): mixed { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $meta_value; } @@ -153,6 +174,21 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user return ! is_protected_meta( $meta_key, 'post' ); } + /** + * Adapter for the narrower `johnbillion/args` auth callback signature. + * + * @since 0.1.0 + * + * @param bool $allowed Whether the user can add the post meta. + * @param string $meta_key The meta key. + * @param string $object_type Object type. + * @param string $object_subtype Object subtype. + * @return bool Whether the user is allowed to edit meta. + */ + public function auth_callback( bool $allowed, string $meta_key, string $object_type, string $object_subtype = '' ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->auth( $allowed, $meta_key, 0, 0, '', [] ); + } + /** * Whether data associated with this meta key can be considered public and * should be accessible via the REST API. @@ -163,11 +199,11 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user * @since 0.1.0 * * @return bool|array{ - * schema: mixed[], - * prepare_callback: callable(mixed,\WP_REST_Request,mixed[]): mixed, + * schema: array, + * prepare_callback: callable(mixed,\WP_REST_Request>,array): mixed, * } */ - protected function show_in_rest() { + protected function show_in_rest(): array|bool { return false; } } diff --git a/inc/TermMeta.php b/inc/TermMeta.php index 15b5a4f..07f69da 100644 --- a/inc/TermMeta.php +++ b/inc/TermMeta.php @@ -23,7 +23,7 @@ abstract class TermMeta implements Registrable, TermMetaInterface { /** * Term meta key. */ - public const KEY = null; + public const string KEY = ''; /** * Registers the object. @@ -33,10 +33,30 @@ abstract class TermMeta implements Registrable, TermMetaInterface { * @return bool Whether the term meta was registered successfully. */ public function register(): bool { + if ( static::KEY === '' ) { + return false; + } + + /** + * @phpstan-var array{ + * object_subtype?: string, + * type?: string, + * label?: string, + * description?: string, + * single?: bool, + * default?: mixed, + * sanitize_callback?: callable, + * auth_callback?: callable, + * show_in_rest?: bool|array, + * revisions_enabled?: bool, + * } $args + */ + $args = $this->get_args()->toArray(); + return register_term_meta( $this->taxonomy(), static::KEY, - $this->get_args()->toArray() + $args ); } @@ -57,7 +77,7 @@ protected function get_args(): TermMetaArgs { $args->single = $this->is_single(); $args->default = $this->default(); $args->sanitize_callback = [ $this, 'sanitize' ]; - $args->auth_callback = [ $this, 'auth' ]; + $args->auth_callback = [ $this, 'auth_callback' ]; $args->show_in_rest = $this->show_in_rest(); return $args; @@ -85,6 +105,7 @@ protected function taxonomy(): string { * * @return string Type of the data. */ + /** @return 'array'|'boolean'|'integer'|'number'|'object'|'string' */ protected function type(): string { return TermMetaArgs::TYPE_STRING; } @@ -118,7 +139,7 @@ protected function is_single(): bool { * * @return mixed The default value. */ - protected function default() { + protected function default(): mixed { return ''; } @@ -132,7 +153,7 @@ protected function default() { * @param string $object_type Object type. * @return mixed Sanitized meta value. */ - public function sanitize( $meta_value, string $meta_key, string $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public function sanitize( mixed $meta_value, string $meta_key, string $object_type ): mixed { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $meta_value; } @@ -153,6 +174,21 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user return ! is_protected_meta( $meta_key, 'term' ); } + /** + * Adapter for the narrower `johnbillion/args` auth callback signature. + * + * @since 0.1.0 + * + * @param bool $allowed Whether the user can add the term meta. + * @param string $meta_key The meta key. + * @param string $object_type Object type. + * @param string $object_subtype Object subtype. + * @return bool Whether the user is allowed to edit meta. + */ + public function auth_callback( bool $allowed, string $meta_key, string $object_type, string $object_subtype = '' ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->auth( $allowed, $meta_key, 0, 0, '', [] ); + } + /** * Whether data associated with this meta key can be considered public and * should be accessible via the REST API. @@ -163,11 +199,11 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user * @since 0.1.0 * * @return bool|array{ - * schema: mixed[], - * prepare_callback: callable(mixed,\WP_REST_Request,mixed[]): mixed, + * schema: array, + * prepare_callback: callable(mixed,\WP_REST_Request>,array): mixed, * } */ - protected function show_in_rest() { + protected function show_in_rest(): array|bool { return false; } } From 5e2f4a3a7e839738a71425a9b0a41216ac4138cc Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 16:20:47 +0100 Subject: [PATCH 15/25] Type TermMetaAdminUI and taxonomy contract --- inc/Contracts/Taxonomy.php | 6 ++++++ inc/TermMetaAdminUI.php | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/inc/Contracts/Taxonomy.php b/inc/Contracts/Taxonomy.php index 7d587d1..d83765c 100644 --- a/inc/Contracts/Taxonomy.php +++ b/inc/Contracts/Taxonomy.php @@ -13,4 +13,10 @@ * @since 0.3.2 */ interface Taxonomy { + /** + * Taxonomy name. + * + * Implementations are expected to override this. + */ + public const string NAME = ''; } diff --git a/inc/TermMetaAdminUI.php b/inc/TermMetaAdminUI.php index 47ee23b..e6e4fb7 100644 --- a/inc/TermMetaAdminUI.php +++ b/inc/TermMetaAdminUI.php @@ -26,14 +26,14 @@ abstract class TermMetaAdminUI implements Registrable { * * @var \Required\Common\Contracts\Taxonomy */ - protected $taxonomy; + protected Taxonomy $taxonomy; /** * The term meta. * * @var \Required\Common\Contracts\TermMeta */ - protected $term_meta; + protected TermMeta $term_meta; /** * Constructor. @@ -69,7 +69,7 @@ public function register(): bool { // List table. add_filter( "manage_edit-{$taxonomy}_columns", [ $this, 'add_column' ] ); add_filter( "manage_edit-{$taxonomy}_sortable_columns", [ $this, 'add_sortable_column' ] ); - add_action( "manage_edit-{$taxonomy}_custom_column", [ $this, 'render_column' ] ); + add_action( "manage_edit-{$taxonomy}_custom_column", [ $this, 'render_column' ], 10, 2 ); return true; } @@ -81,7 +81,7 @@ public function register(): bool { * * @param int $term_id Term ID. */ - abstract public function add( int $term_id ); + abstract public function add( int $term_id ): void; /** * Updates an existing term meta. @@ -90,14 +90,14 @@ abstract public function add( int $term_id ); * * @param int $term_id Term ID. */ - abstract public function update( int $term_id ); + abstract public function update( int $term_id ): void; /** * Renders field when adding a new term. * * @since 0.1.0 */ - abstract public function render_add_field(); + abstract public function render_add_field(): void; /** * Renders field when editing a term. @@ -106,15 +106,15 @@ abstract public function render_add_field(); * * @param \WP_Term $term Current term object. */ - abstract public function render_edit_field( WP_Term $term ); + abstract public function render_edit_field( WP_Term $term ): void; /** * Adds the term meta to the list of list table columns. * * @since 0.1.0 * - * @param array $columns An array of columns. - * @return array An array of columns. + * @param array $columns An array of columns. + * @return array An array of columns. */ public function add_column( array $columns ): array { return $columns; @@ -125,8 +125,8 @@ public function add_column( array $columns ): array { * * @since 0.1.0 * - * @param array $sortable_columns An array of sortable columns. - * @return array An array of sortable columns. + * @param array $sortable_columns An array of sortable columns. + * @return array An array of sortable columns. */ public function add_sortable_column( array $sortable_columns ): array { return $sortable_columns; @@ -140,5 +140,5 @@ public function add_sortable_column( array $sortable_columns ): array { * @param string $column_name Name of the column. * @param int $term_id Term ID. */ - public function render_column( string $column_name, int $term_id ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public function render_column( string $column_name, int $term_id ): void {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable } From c5349537fa48ffc86ff917a530f5b4e2f0657fab Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 17:57:44 +0100 Subject: [PATCH 16/25] Exclude Squiz.Commenting.VariableComment.MissingVar rule --- phpcs.xml.dist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 91b7fb8..db6ba7c 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -4,7 +4,9 @@ . - + + + From a3ddf9b2d2400bfd03eff1209eab35d793b1b61a Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 17:59:41 +0100 Subject: [PATCH 17/25] Fix PHPCS issues --- inc/Admin/Page.php | 8 -------- inc/Contracts/ShortcodeView.php | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/inc/Admin/Page.php b/inc/Admin/Page.php index a3eea79..3fc2e6c 100644 --- a/inc/Admin/Page.php +++ b/inc/Admin/Page.php @@ -24,8 +24,6 @@ class Page implements Registrable { * @since 0.1.0 * * @see is_admin() - * - * @var int */ public const int ADMIN = 1; @@ -35,8 +33,6 @@ class Page implements Registrable { * @since 0.1.0 * * @see is_network_admin() - * - * @var int */ public const int ADMIN_NETWORK = 2; @@ -46,8 +42,6 @@ class Page implements Registrable { * @since 0.1.0 * * @see is_user_admin() - * - * @var int */ public const int ADMIN_USER = 3; @@ -181,8 +175,6 @@ public function __construct( int $admin, string $title, string $menu_title, stri * Returns the action for registering the page. * * @since 0.1.0 - * - * @return string */ private function get_action(): string { switch ( $this->admin ) { diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php index 8f8196b..42de12c 100644 --- a/inc/Contracts/ShortcodeView.php +++ b/inc/Contracts/ShortcodeView.php @@ -22,8 +22,8 @@ interface ShortcodeView { * @since 0.1.0 * * @param array $attributes Attributes of the shortcode. - * @param string|null $content The content. - * @param string $shortcode_tag The shortcode tag. + * @param string|null $content The content. + * @param string $shortcode_tag The shortcode tag. * @return string HTML output of the shortcode. */ public function render( array $attributes, ?string $content, string $shortcode_tag ): string; From f16ef04a91332597ed4ac056d5105d703d1de8d7 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 18:02:51 +0100 Subject: [PATCH 18/25] Remove Shortcode and ShortcodeView classes --- inc/Contracts/ShortcodeView.php | 30 ---------------- inc/Shortcode.php | 62 --------------------------------- 2 files changed, 92 deletions(-) delete mode 100644 inc/Contracts/ShortcodeView.php delete mode 100644 inc/Shortcode.php diff --git a/inc/Contracts/ShortcodeView.php b/inc/Contracts/ShortcodeView.php deleted file mode 100644 index 42de12c..0000000 --- a/inc/Contracts/ShortcodeView.php +++ /dev/null @@ -1,30 +0,0 @@ - $attributes Attributes of the shortcode. - * @param string|null $content The content. - * @param string $shortcode_tag The shortcode tag. - * @return string HTML output of the shortcode. - */ - public function render( array $attributes, ?string $content, string $shortcode_tag ): string; -} diff --git a/inc/Shortcode.php b/inc/Shortcode.php deleted file mode 100644 index 897d42e..0000000 --- a/inc/Shortcode.php +++ /dev/null @@ -1,62 +0,0 @@ -view = $view; - } - - /** - * Creates a shortcode object. - * - * @since 0.1.0 - * - * @return bool Whether shortcode was registered successfully. - */ - public function register(): bool { - if ( '' === static::TAG ) { - return false; - } - - add_shortcode( - static::TAG, - [ $this->view, 'render' ] - ); - return shortcode_exists( static::TAG ); - } -} From 607283939534745d5bfd7df825895474c7d5a3b3 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 23 Jan 2026 18:28:06 +0100 Subject: [PATCH 19/25] Document factory-based registration --- FACTORIES.md | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 FACTORIES.md diff --git a/FACTORIES.md b/FACTORIES.md new file mode 100644 index 0000000..999ba3f --- /dev/null +++ b/FACTORIES.md @@ -0,0 +1,157 @@ +# Factories statt Instanzen (API-Vorschlag) + +Dieses Dokument beschreibt eine optionale, **lazy** Registration-API für diese Library: Statt alle `Registrable`-Objekte sofort zu instanziieren, werden sie als **Factory** (Closure) hinterlegt und erst beim `bootstrap()` (oder beim ersten Zugriff) gebaut. + +> Hinweis: Das ist eine Dokumentation/Design-Notiz. Die API ist (noch) nicht Bestandteil der veröffentlichten Version, solange sie nicht implementiert wurde. + +## Motivation + +In realen WordPress-Codebasen sieht man häufig Registrations wie in der README: Viele `new …()` in einer Bootstrap-Funktion. + +Probleme dabei: + +- **Eager instantiation**: Teure Konstruktoren laufen auch dann, wenn der zugehörige Codepfad nie benutzt wird (z.B. Admin-only auf Frontend Requests). +- **Schwierigere Performance-Optimierung**: Lazy Loading und Kontext-Gating (admin/rest/cli) muss man händisch außerhalb des Containers umsetzen. +- **Wiring/Abhängigkeiten**: Sobald ein `Registrable` Abhängigkeiten hat, wächst Boilerplate im Bootstrap-Code. + +Das „Factories statt Instanzen“-Pattern löst das, ohne einen vollständigen DI-Container einzuführen. + +## Ziele + +- Minimal-invasiv: bestehende Nutzung bleibt möglich. +- Kein hard dependency auf DI-Frameworks. +- Lazy Instantiation per Factory. +- Optional: einfache „Resolver“-Semantik (Factory wird nur einmal ausgeführt, Ergebnis gecached). + +## Vorgeschlagene Container-API + +### Begriffe + +- **Entry-ID**: Ein eindeutiger Schlüssel im Container (z.B. `PostType::NAME`, `PostMeta::KEY`). +- **Registrable**: Objekt mit `register(): bool` (aus `Required\Common\Contracts\Registrable`). +- **Factory**: `callable(): Registrable` (typischerweise `fn() => new …()`), die ein `Registrable` erzeugt. + +### Methoden + +#### `add(string $id, Registrable $registrable): void` + +- Behält das aktuelle Verhalten bei. +- Speichert eine bereits instanziierte Implementierung. + +#### `addFactory(string $id, callable $factory): void` + +- Signatur-Idee: + +```php +/** @param callable(): \Required\Common\Contracts\Registrable $factory */ +public function addFactory(string $id, callable $factory): void; +``` + +- Speichert eine Factory, ohne das Objekt sofort zu bauen. + +#### `get(string $id): ?Registrable` (optional, aber praktisch) + +- Liefert die Instanz zurück. +- Wenn der Eintrag als Factory hinterlegt ist: + - Factory wird genau einmal ausgeführt. + - Ergebnis wird im Container gecached. + +#### `bootstrap(): void` + +- Löst alle Entries auf (Instanzen + Factories). +- Ruft für jede Instanz `register()` auf. +- Idealerweise idempotent (ein Container bootstrapped nur einmal). + +## Semantik (Details) + +- **Caching**: Factories werden beim ersten Resolve zu einer Instanz „materialisiert“. +- **Fehlerfälle**: + - Duplicate IDs: entweder überschreiben (aktuelles Verhalten?) oder Exception/Fehler. + - Factory gibt kein `Registrable` zurück: Exception/Fehler. + - Factory wirft Exception: Exception nach außen oder als `false`-Register-Ergebnis behandeln (Design-Entscheid). + +## Beispiele + +### 1) Post Types (lazy) + +Vorher: + +```php +$post_types = new Container(); +$post_types->add( Profession::NAME, new Profession() ); +$post_types->bootstrap(); +``` + +Mit Factory: + +```php +$post_types = new Container(); +$post_types->addFactory( Profession::NAME, static fn () => new Profession() ); +$post_types->bootstrap(); +``` + +### 2) Post Meta (viele Entries, lazy) + +```php +$post_meta = new Container(); + +$post_meta->addFactory( PostMeta\ImportID::KEY, static fn () => new PostMeta\ImportID() ); +$post_meta->addFactory( PostMeta\ImportTimestamp::KEY, static fn () => new PostMeta\ImportTimestamp() ); + +$post_meta->bootstrap(); +``` + +### 3) Abhängigkeiten über Closures „injizieren“ (Minimal-DI) + +```php +$client = new ApiClient( /* … */ ); +$logger = new Logger( /* … */ ); + +$registrables = new Container(); +$registrables->addFactory( + SomeIntegration::ID, + static fn () => new SomeIntegration($client, $logger) +); + +$registrables->bootstrap(); +``` + +Vorteil: Keine externe DI-Abhängigkeit; trotzdem „injection“ über Scope. + +### 4) Kontext-Gating (Pattern) + +Dieses Pattern ist bewusst „WordPress-nah“ und lässt sich bereits ohne Container-Erweiterung nutzen: + +```php +add_action( + 'admin_init', + static function () { + $admin = new Container(); + $admin->addFactory( AdminOnlyThing::ID, static fn () => new AdminOnlyThing() ); + $admin->bootstrap(); + } +); +``` + +Optionaler Ausbau (wenn gewünscht): + +- `bootstrap(callable $filter)` oder `bootstrapOnly(array $ids)` +- „Tags“/„Groups“ pro Entry (z.B. `->tag('admin')`) + +## Migration von Instanzen → Factories + +- **Mechanisch**: + - `add($id, new X())` → `addFactory($id, fn() => new X())` +- **Wann lohnt es sich besonders?** + - Wenn Konstruktoren IO machen (HTTP/DB), Konfiguration lesen, große Arrays bauen. + - Wenn ein Registrable nur in bestimmten Contexts benötigt wird. + +## Abgrenzung zu einem vollwertigen DI-Container + +Diese API ist absichtlich „klein“: + +- Kein Autowiring. +- Kein Reflection-basiertes Resolution-System. +- Kein Lifecycle-Management jenseits von „factory → instance“. + +Wenn ein Projekt später mehr braucht, kann man weiterhin einen DI-Container im Projekt nutzen und Factories in diesen Container delegieren. From 640ae89f039b4f12ed65ee17b8f26145fc7c5381 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Sun, 25 Jan 2026 13:44:29 +0100 Subject: [PATCH 20/25] Remove FACTORIES.md documentation file --- FACTORIES.md | 157 --------------------------------------------------- 1 file changed, 157 deletions(-) delete mode 100644 FACTORIES.md diff --git a/FACTORIES.md b/FACTORIES.md deleted file mode 100644 index 999ba3f..0000000 --- a/FACTORIES.md +++ /dev/null @@ -1,157 +0,0 @@ -# Factories statt Instanzen (API-Vorschlag) - -Dieses Dokument beschreibt eine optionale, **lazy** Registration-API für diese Library: Statt alle `Registrable`-Objekte sofort zu instanziieren, werden sie als **Factory** (Closure) hinterlegt und erst beim `bootstrap()` (oder beim ersten Zugriff) gebaut. - -> Hinweis: Das ist eine Dokumentation/Design-Notiz. Die API ist (noch) nicht Bestandteil der veröffentlichten Version, solange sie nicht implementiert wurde. - -## Motivation - -In realen WordPress-Codebasen sieht man häufig Registrations wie in der README: Viele `new …()` in einer Bootstrap-Funktion. - -Probleme dabei: - -- **Eager instantiation**: Teure Konstruktoren laufen auch dann, wenn der zugehörige Codepfad nie benutzt wird (z.B. Admin-only auf Frontend Requests). -- **Schwierigere Performance-Optimierung**: Lazy Loading und Kontext-Gating (admin/rest/cli) muss man händisch außerhalb des Containers umsetzen. -- **Wiring/Abhängigkeiten**: Sobald ein `Registrable` Abhängigkeiten hat, wächst Boilerplate im Bootstrap-Code. - -Das „Factories statt Instanzen“-Pattern löst das, ohne einen vollständigen DI-Container einzuführen. - -## Ziele - -- Minimal-invasiv: bestehende Nutzung bleibt möglich. -- Kein hard dependency auf DI-Frameworks. -- Lazy Instantiation per Factory. -- Optional: einfache „Resolver“-Semantik (Factory wird nur einmal ausgeführt, Ergebnis gecached). - -## Vorgeschlagene Container-API - -### Begriffe - -- **Entry-ID**: Ein eindeutiger Schlüssel im Container (z.B. `PostType::NAME`, `PostMeta::KEY`). -- **Registrable**: Objekt mit `register(): bool` (aus `Required\Common\Contracts\Registrable`). -- **Factory**: `callable(): Registrable` (typischerweise `fn() => new …()`), die ein `Registrable` erzeugt. - -### Methoden - -#### `add(string $id, Registrable $registrable): void` - -- Behält das aktuelle Verhalten bei. -- Speichert eine bereits instanziierte Implementierung. - -#### `addFactory(string $id, callable $factory): void` - -- Signatur-Idee: - -```php -/** @param callable(): \Required\Common\Contracts\Registrable $factory */ -public function addFactory(string $id, callable $factory): void; -``` - -- Speichert eine Factory, ohne das Objekt sofort zu bauen. - -#### `get(string $id): ?Registrable` (optional, aber praktisch) - -- Liefert die Instanz zurück. -- Wenn der Eintrag als Factory hinterlegt ist: - - Factory wird genau einmal ausgeführt. - - Ergebnis wird im Container gecached. - -#### `bootstrap(): void` - -- Löst alle Entries auf (Instanzen + Factories). -- Ruft für jede Instanz `register()` auf. -- Idealerweise idempotent (ein Container bootstrapped nur einmal). - -## Semantik (Details) - -- **Caching**: Factories werden beim ersten Resolve zu einer Instanz „materialisiert“. -- **Fehlerfälle**: - - Duplicate IDs: entweder überschreiben (aktuelles Verhalten?) oder Exception/Fehler. - - Factory gibt kein `Registrable` zurück: Exception/Fehler. - - Factory wirft Exception: Exception nach außen oder als `false`-Register-Ergebnis behandeln (Design-Entscheid). - -## Beispiele - -### 1) Post Types (lazy) - -Vorher: - -```php -$post_types = new Container(); -$post_types->add( Profession::NAME, new Profession() ); -$post_types->bootstrap(); -``` - -Mit Factory: - -```php -$post_types = new Container(); -$post_types->addFactory( Profession::NAME, static fn () => new Profession() ); -$post_types->bootstrap(); -``` - -### 2) Post Meta (viele Entries, lazy) - -```php -$post_meta = new Container(); - -$post_meta->addFactory( PostMeta\ImportID::KEY, static fn () => new PostMeta\ImportID() ); -$post_meta->addFactory( PostMeta\ImportTimestamp::KEY, static fn () => new PostMeta\ImportTimestamp() ); - -$post_meta->bootstrap(); -``` - -### 3) Abhängigkeiten über Closures „injizieren“ (Minimal-DI) - -```php -$client = new ApiClient( /* … */ ); -$logger = new Logger( /* … */ ); - -$registrables = new Container(); -$registrables->addFactory( - SomeIntegration::ID, - static fn () => new SomeIntegration($client, $logger) -); - -$registrables->bootstrap(); -``` - -Vorteil: Keine externe DI-Abhängigkeit; trotzdem „injection“ über Scope. - -### 4) Kontext-Gating (Pattern) - -Dieses Pattern ist bewusst „WordPress-nah“ und lässt sich bereits ohne Container-Erweiterung nutzen: - -```php -add_action( - 'admin_init', - static function () { - $admin = new Container(); - $admin->addFactory( AdminOnlyThing::ID, static fn () => new AdminOnlyThing() ); - $admin->bootstrap(); - } -); -``` - -Optionaler Ausbau (wenn gewünscht): - -- `bootstrap(callable $filter)` oder `bootstrapOnly(array $ids)` -- „Tags“/„Groups“ pro Entry (z.B. `->tag('admin')`) - -## Migration von Instanzen → Factories - -- **Mechanisch**: - - `add($id, new X())` → `addFactory($id, fn() => new X())` -- **Wann lohnt es sich besonders?** - - Wenn Konstruktoren IO machen (HTTP/DB), Konfiguration lesen, große Arrays bauen. - - Wenn ein Registrable nur in bestimmten Contexts benötigt wird. - -## Abgrenzung zu einem vollwertigen DI-Container - -Diese API ist absichtlich „klein“: - -- Kein Autowiring. -- Kein Reflection-basiertes Resolution-System. -- Kein Lifecycle-Management jenseits von „factory → instance“. - -Wenn ein Projekt später mehr braucht, kann man weiterhin einen DI-Container im Projekt nutzen und Factories in diesen Container delegieren. From 1d8df618886863684886c1e0158b5a78720b3ebb Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Mon, 26 Jan 2026 16:02:24 +0100 Subject: [PATCH 21/25] Refactor render_column method to use filter instead of action and update parameters --- inc/TermMetaAdminUI.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/inc/TermMetaAdminUI.php b/inc/TermMetaAdminUI.php index e6e4fb7..4fc3b68 100644 --- a/inc/TermMetaAdminUI.php +++ b/inc/TermMetaAdminUI.php @@ -69,7 +69,7 @@ public function register(): bool { // List table. add_filter( "manage_edit-{$taxonomy}_columns", [ $this, 'add_column' ] ); add_filter( "manage_edit-{$taxonomy}_sortable_columns", [ $this, 'add_sortable_column' ] ); - add_action( "manage_edit-{$taxonomy}_custom_column", [ $this, 'render_column' ], 10, 2 ); + add_filter( "manage_{$taxonomy}_custom_column", [ $this, 'render_column' ], 10, 3 ); return true; } @@ -137,8 +137,12 @@ public function add_sortable_column( array $sortable_columns ): array { * * @since 0.1.0 * + * @param string $output Custom column output. Default empty. * @param string $column_name Name of the column. * @param int $term_id Term ID. + * @return string The column output. */ - public function render_column( string $column_name, int $term_id ): void {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public function render_column( string $output, string $column_name, int $term_id ): string { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $output; + } } From d7bb20f8f55faefb25eb5613ed2b80d5b0c0a8a8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:21:28 +0100 Subject: [PATCH 22/25] Fix PHPDoc annotations and add PostType NAME constant (#8) * Initial plan * Fix PHPDoc annotations and add PostType NAME constant Co-authored-by: ocean90 <617637+ocean90@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ocean90 <617637+ocean90@users.noreply.github.com> --- inc/Admin/Page.php | 4 ++-- inc/Contracts/PostType.php | 6 ++++++ inc/PostMeta.php | 3 +-- inc/TermMeta.php | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/inc/Admin/Page.php b/inc/Admin/Page.php index 3fc2e6c..21756a6 100644 --- a/inc/Admin/Page.php +++ b/inc/Admin/Page.php @@ -131,7 +131,7 @@ class Page implements Registrable { * * @since 0.1.0 * - * @var string + * @var string|false */ private string|false $hook_name = false; @@ -245,7 +245,7 @@ private function get_callback(): \Closure { * * @since 0.1.0 * - * @return string The hook name of the menu page. + * @return string|false The hook name of the menu page. */ public function get_hook_name(): string|false { return $this->hook_name; diff --git a/inc/Contracts/PostType.php b/inc/Contracts/PostType.php index 0875984..e3c53c1 100644 --- a/inc/Contracts/PostType.php +++ b/inc/Contracts/PostType.php @@ -13,4 +13,10 @@ * @since 0.3.2 */ interface PostType { + /** + * Post type name. + * + * Implementations are expected to override this. + */ + public const string NAME = ''; } diff --git a/inc/PostMeta.php b/inc/PostMeta.php index 4061a4d..bc08451 100644 --- a/inc/PostMeta.php +++ b/inc/PostMeta.php @@ -103,9 +103,8 @@ protected function post_type(): string { * * @since 0.1.0 * - * @return string Type of the data. + * @return 'array'|'boolean'|'integer'|'number'|'object'|'string' Type of the data. */ - /** @return 'array'|'boolean'|'integer'|'number'|'object'|'string' */ protected function type(): string { return PostMetaArgs::TYPE_STRING; } diff --git a/inc/TermMeta.php b/inc/TermMeta.php index 07f69da..89d71a4 100644 --- a/inc/TermMeta.php +++ b/inc/TermMeta.php @@ -103,9 +103,8 @@ protected function taxonomy(): string { * * @since 0.1.0 * - * @return string Type of the data. + * @return 'array'|'boolean'|'integer'|'number'|'object'|'string' Type of the data. */ - /** @return 'array'|'boolean'|'integer'|'number'|'object'|'string' */ protected function type(): string { return TermMetaArgs::TYPE_STRING; } From 4bacc00076aff80122ff8d3a2170256ecc283584 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 13 Mar 2026 17:08:46 +0100 Subject: [PATCH 23/25] Update Args --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d4e165..aebb7aa 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "php": ">=8.3", - "johnbillion/args": "^1.2" + "johnbillion/args": "^2.4" }, "require-dev": { "szepeviktor/phpstan-wordpress": "^1.0 || ^2.0" From 1f9818dc6c54a3c62aaa579cd2c3fac99fcc3ee9 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 13 Mar 2026 17:17:16 +0100 Subject: [PATCH 24/25] Add label method to PostMeta class and update args in register method --- inc/PostMeta.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/inc/PostMeta.php b/inc/PostMeta.php index bc08451..b4daf79 100644 --- a/inc/PostMeta.php +++ b/inc/PostMeta.php @@ -73,6 +73,7 @@ protected function get_args(): PostMetaArgs { $args = new PostMetaArgs(); $args->type = $this->type(); + $args->label = $this->label(); $args->description = $this->description(); $args->single = $this->is_single(); $args->default = $this->default(); @@ -109,6 +110,17 @@ protected function type(): string { return PostMetaArgs::TYPE_STRING; } + /** + * A label for the meta key. + * + * @since 0.3.0 + * + * @return string Label for the meta key. + */ + protected function label(): string { + return static::KEY; + } + /** * A description of the data attached to this meta key. * From 67c0f8a4474fb2f8730172ced22a8540333c3a00 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Fri, 13 Mar 2026 17:40:28 +0100 Subject: [PATCH 25/25] Refactor auth callback method in PostMeta and TermMeta classes to use 'auth' instead of 'auth_callback' and remove outdated documentation --- inc/PostMeta.php | 17 +---------------- inc/TermMeta.php | 17 +---------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/inc/PostMeta.php b/inc/PostMeta.php index b4daf79..697fd5c 100644 --- a/inc/PostMeta.php +++ b/inc/PostMeta.php @@ -78,7 +78,7 @@ protected function get_args(): PostMetaArgs { $args->single = $this->is_single(); $args->default = $this->default(); $args->sanitize_callback = [ $this, 'sanitize' ]; - $args->auth_callback = [ $this, 'auth_callback' ]; + $args->auth_callback = [ $this, 'auth' ]; $args->show_in_rest = $this->show_in_rest(); return $args; @@ -185,21 +185,6 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user return ! is_protected_meta( $meta_key, 'post' ); } - /** - * Adapter for the narrower `johnbillion/args` auth callback signature. - * - * @since 0.1.0 - * - * @param bool $allowed Whether the user can add the post meta. - * @param string $meta_key The meta key. - * @param string $object_type Object type. - * @param string $object_subtype Object subtype. - * @return bool Whether the user is allowed to edit meta. - */ - public function auth_callback( bool $allowed, string $meta_key, string $object_type, string $object_subtype = '' ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return $this->auth( $allowed, $meta_key, 0, 0, '', [] ); - } - /** * Whether data associated with this meta key can be considered public and * should be accessible via the REST API. diff --git a/inc/TermMeta.php b/inc/TermMeta.php index 89d71a4..3832dd6 100644 --- a/inc/TermMeta.php +++ b/inc/TermMeta.php @@ -77,7 +77,7 @@ protected function get_args(): TermMetaArgs { $args->single = $this->is_single(); $args->default = $this->default(); $args->sanitize_callback = [ $this, 'sanitize' ]; - $args->auth_callback = [ $this, 'auth_callback' ]; + $args->auth_callback = [ $this, 'auth' ]; $args->show_in_rest = $this->show_in_rest(); return $args; @@ -173,21 +173,6 @@ public function auth( bool $allowed, string $meta_key, int $object_id, int $user return ! is_protected_meta( $meta_key, 'term' ); } - /** - * Adapter for the narrower `johnbillion/args` auth callback signature. - * - * @since 0.1.0 - * - * @param bool $allowed Whether the user can add the term meta. - * @param string $meta_key The meta key. - * @param string $object_type Object type. - * @param string $object_subtype Object subtype. - * @return bool Whether the user is allowed to edit meta. - */ - public function auth_callback( bool $allowed, string $meta_key, string $object_type, string $object_subtype = '' ): bool { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return $this->auth( $allowed, $meta_key, 0, 0, '', [] ); - } - /** * Whether data associated with this meta key can be considered public and * should be accessible via the REST API.