Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PHP Scaffold for CPTs and Taxonomies #175

Closed
1 task done
darylldoyle opened this issue Apr 28, 2023 · 1 comment · Fixed by #218
Closed
1 task done

Add PHP Scaffold for CPTs and Taxonomies #175

darylldoyle opened this issue Apr 28, 2023 · 1 comment · Fixed by #218

Comments

@darylldoyle
Copy link
Collaborator

Is your enhancement related to a problem? Please describe.

All new 10up projects are based on this repo, but there is very little PHP scaffolding in place. This means that each different project ends up with a slightly different implementation for post types, taxonomies, REST endpoints etc.

This has two effects:

  • Slows down the initial setup of a project, as the tech lead has to put these scaffolds in place.
  • Increases the cognitive load for engineers that are switching between projects, as they have to learn the differences in setups.

Designs

Since the auto-initialisation of modules was merged in #158, it's allowed us to create much more modular scaffolds, with no requirement for factory classes etc.

I propose introducing abstract classes that contain all the base information needed to register a CPT/Taxonomy, which can then be extended to implement project-specific ones.

For example, the following abstract post type would then allow us to easily implement further ones:

<?php
/**
 * AbstractPostType
 *
 * @package TenUpPlugin
 */

namespace TenUpPlugin\PostTypes;

use TenUpPlugin\Module;

/**
 * Abstract class for post types.
 */
abstract class AbstractPostType extends Module {

	/**
	 * Get the post type name.
	 *
	 * @return string
	 */
	abstract public function get_name();

	/**
	 * Get the singular post type label.
	 *
	 * @return string
	 */
	abstract public function get_singular_label();

	/**
	 * Get the plural post type label.
	 *
	 * @return string
	 */
	abstract public function get_plural_label();

	/**
	 * Default post type supported feature names.
	 *
	 * @return array
	 */
	public function get_editor_supports() {
		$supports = [
			'title',
			'editor',
			'author',
			'thumbnail',
			'excerpt',
			'revisions',
		];

		return $supports;
	}

	/**
	 * Get the options for the post type.
	 *
	 * @return array
	 */
	public function get_options() {
		$options = [
			'labels'            => $this->get_labels(),
			'public'            => true,
			'has_archive'       => true,
			'show_ui'           => true,
			'show_in_menu'      => true,
			'show_in_nav_menus' => false,
			'show_in_rest'      => true,
			'supports'          => $this->get_editor_supports(),
		];

		return $options;
	}

	/**
	 * Get the labels for the post type.
	 *
	 * @return array
	 */
	public function get_labels() {
		$plural_label   = $this->get_plural_label();
		$singular_label = $this->get_singular_label();

		// phpcs:disable -- ignoring template strings without translators placeholder since this is dynamic
		$labels = array(
			'name'                     => $plural_label,
			// Already translated via get_plural_label().
			'singular_name'            => $singular_label,
			// Already translated via get_singular_label().
			'add_new_item'             => sprintf( __( 'Add New %s', 'tenup-plugin' ), $singular_label ),
			'edit_item'                => sprintf( __( 'Edit %s', 'tenup-plugin' ), $singular_label ),
			'new_item'                 => sprintf( __( 'New %s', 'tenup-plugin' ), $singular_label ),
			'view_item'                => sprintf( __( 'View %s', 'tenup-plugin' ), $singular_label ),
			'view_items'               => sprintf( __( 'View %s', 'tenup-plugin' ), $plural_label ),
			'search_items'             => sprintf( __( 'Search %s', 'tenup-plugin' ), $plural_label ),
			'not_found'                => sprintf( __( 'No %s found.', 'tenup-plugin' ), strtolower( $plural_label ) ),
			'not_found_in_trash'       => sprintf( __( 'No %s found in Trash.', 'tenup-plugin' ), strtolower( $plural_label ) ),
			'parent_item_colon'        => sprintf( __( 'Parent %s:', 'tenup-plugin' ), $plural_label ),
			'all_items'                => sprintf( __( 'All %s', 'tenup-plugin' ), $plural_label ),
			'archives'                 => sprintf( __( '%s Archives', 'tenup-plugin' ), $singular_label ),
			'attributes'               => sprintf( __( '%s Attributes', 'tenup-plugin' ), $singular_label ),
			'insert_into_item'         => sprintf( __( 'Insert into %s', 'tenup-plugin' ), strtolower( $singular_label ) ),
			'uploaded_to_this_item'    => sprintf( __( 'Uploaded to this %s', 'tenup-plugin' ), strtolower( $singular_label ) ),
			'filter_items_list'        => sprintf( __( 'Filter %s list', 'tenup-plugin' ), strtolower( $plural_label ) ),
			'items_list_navigation'    => sprintf( __( '%s list navigation', 'tenup-plugin' ), $plural_label ),
			'items_list'               => sprintf( __( '%s list', 'tenup-plugin' ), $plural_label ),
			'item_published'           => sprintf( __( '%s published.', 'tenup-plugin' ), $singular_label ),
			'item_published_privately' => sprintf( __( '%s published privately.', 'tenup-plugin' ), $singular_label ),
			'item_reverted_to_draft'   => sprintf( __( '%s reverted to draft.', 'tenup-plugin' ), $singular_label ),
			'item_scheduled'           => sprintf( __( '%s scheduled.', 'tenup-plugin' ), $singular_label ),
			'item_updated'             => sprintf( __( '%s updated.', 'tenup-plugin' ), $singular_label ),
			'menu_name'                => $plural_label,
			'name_admin_bar'           => $singular_label,
		);
		// phpcs:enable

		return $labels;
	}

	/**
	 * Registers a post type and associates its taxonomies.
	 *
	 * @uses $this->get_name() to get the post's type name.
	 * @return Bool Whether this theme has supports for this post type.
	 */
	public function register() {
		$this->register_post_type();
		$this->register_taxonomies();

		$this->after_register();

		return true;
	}

	/**
	 * Registers the current post type with WordPress.
	 *
	 * @return void
	 */
	public function register_post_type() {
		register_post_type(
			$this->get_name(),
			$this->get_options()
		);
	}

	/**
	 * Registers the taxonomies declared with the current post type.
	 *
	 * @return void
	 */
	public function register_taxonomies() {
		$taxonomies = $this->get_supported_taxonomies();

		$object_type = $this->get_name();

		if ( ! empty( $taxonomies ) ) {
			foreach ( $taxonomies as $taxonomy ) {
				register_taxonomy_for_object_type(
					$taxonomy,
					$object_type
				);
			}
		}
	}

	/**
	 * Returns the default supported taxonomies. The subclass should declare the
	 * Taxonomies that it supports here if required.
	 *
	 * @return array
	 */
	public function get_supported_taxonomies() {
		return [];
	}

	/**
	 * Run any code after the post type has been registered.
	 *
	 * @return void
	 */
	public function after_register() {
		// Do nothing.
	}

}

To implement a project-specific CPT, we'd then do something like:

<?php
/**
 * Demo Post Type
 *
 * @package TenUpPlugin
 */

namespace TenUpPlugin\PostTypes;

/**
 * Demo post type.
 */
class Demo extends AbstractPostType {

	/**
	 * Get the post type name.
	 *
	 * @return string
	 */
	public function get_name() {
		return 'tenup-demo';
	}

	/**
	 * Get the singular post type label.
	 *
	 * @return string
	 */
	public function get_singular_label() {
		return esc_html__( 'Demo', 'tenup-plugin' );
	}

	/**
	 * Get the plural post type label.
	 *
	 * @return string
	 */
	public function get_plural_label() {
		return esc_html__( 'Demos', 'tenup-plugin' );
	}

	/**
	 * Can the class be registered?
	 *
	 * @return bool
	 */
	public function can_register() {
		return true;
	}

	/**
	 * Supported post type features.
	 *
	 * @return array
	 */
	public function get_editor_supports() {
		return [
			'title',
			'editor',
			'thumbnail',
			'page-attributes',
			'excerpt',
			'revisions',
			'custom-fields',
		];
	}

	/**
	 * Get the options for the post type.
	 *
	 * @return array
	 */
	public function get_options() {
		return array_merge(
			parent::get_options(),
			[
				'hierarchical' => false,
				'rewrite'      => [
					'slug' => 'demo',
				],
			]
		);
	}

	/**
	 * Returns the default supported taxonomies. The subclass should declare the
	 * Taxonomies that it supports here if required.
	 *
	 * @return array
	 */
	public function get_supported_taxonomies() {
		return [
			'tenup-tax-demo',
		];
	}
}

Due to the fact that we have auto-initializing modules, the engineer would only need to create the sub-class and fill it out. There's no need to add anything to a factory, meaning less merge conflicts.

We can also do something very similar for taxonomies.

This solution would provide engineers with:

  • A standardised, extendable way to register post types
  • A pre-existing scaffold within the repo
  • Loss cognitive overhead when switching between projects.

It also gives us a nice lead-in to creating a tool to help scaffold these items quickly, much like has been proposed in #89

Describe alternatives you've considered

Darshan has also submitted #104. I don't feel like this is an alternative to that approach, but it could be complimentary to it by proving a start for that modular scaffold.

Code of Conduct

  • I agree to follow this project's Code of Conduct
@fabiankaegy
Copy link
Member

From my perspective something like this would be very useful because pretty much every single project I've worked on needed multiple CPT's and every project does them slightly differently.

I am in favor of having the CPT's and anything that dictates the information architecture inside a plugin / plugins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants