{"id":37232273,"url":"https://github.com/pwpf/demo","last_synced_at":"2026-01-15T03:48:23.893Z","repository":{"id":57046071,"uuid":"234056305","full_name":"pwpf/demo","owner":"pwpf","description":"PWPF demo app - Plugin WordPress Framework (Boilerplate) with composer","archived":false,"fork":false,"pushed_at":"2022-10-18T13:40:40.000Z","size":518,"stargazers_count":2,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-15T02:55:44.937Z","etag":null,"topics":["boilerplate","demo","mvc","mvc-plugin-boilerplate","router","wordpress","wordpress-plugin"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pwpf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-15T10:31:06.000Z","updated_at":"2022-10-18T13:40:46.000Z","dependencies_parsed_at":"2022-08-24T05:00:21.362Z","dependency_job_id":null,"html_url":"https://github.com/pwpf/demo","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/pwpf/demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwpf%2Fdemo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwpf%2Fdemo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwpf%2Fdemo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwpf%2Fdemo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pwpf","download_url":"https://codeload.github.com/pwpf/demo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pwpf%2Fdemo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28419235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boilerplate","demo","mvc","mvc-plugin-boilerplate","router","wordpress","wordpress-plugin"],"created_at":"2026-01-15T03:48:23.243Z","updated_at":"2026-01-15T03:48:23.865Z","avatar_url":"https://github.com/pwpf.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"### Installation\n\nRelease\n\n    composer create-project pwpf/demo project_name\n    cd project_name\n    ./pwpf-generator.sh\n    composer dump-autoload\n    git config --local core.hooksPath .githooks/\n    composer update\n\nOr dev\n\n    composer create-project pwpf/demo --stability=dev project_name_dev\n    cd project_name_dev\n    ./pwpf-generator.sh\n    composer dump-autoload\n    git config --local core.hooksPath .githooks/\n    composer update\n\n# MVC Plugin Boilerplate for WordPress\n\nWordPress being Event driven system, it is difficult to follow MVC Design Pattern while creating a WordPress Plugin.\nThis project aims to help plugin developers achieve MVC pattern in their coding.\nIf you are new to the term MVC and have never worked with MVC architecture before, I would highly recommend going through this course: https://www.udemy.com/php-mvc-from-scratch/\n\n## Why?\nThe original [WordPress Plugin Boilerplate](https://github.com/DevinVinson/WordPress-Plugin-Boilerplate) is great starting \npoint for creating small plugins. So if your plugin is small, I definitely recommend using that boilerplate. However, as the plugin starts growing \u0026 we add more-n-more features to it, it somewhat becomes challenging to decide where a certain piece of code should go OR how/when to separate different functionalities.\nWhen these things are not clear in long term project to the developer, they end up creating GOD classes that try to do everything.\n\nThe objective of this boilerplate is to separate concerns. Developer gets a chance to write individual `Model`, `View` \u0026 `Controller`. Also, the concern of whether to load a controller/model or not is delegated to `Router`, so that your controller \u0026 model can focus only on what they are supposed to do. \n\nBecause this project is meant to be a boilerplate, it has only those features which are required to build plugin in MVC way - No ORM - No Extra Goodies - No Huge Learning Curve. \n\n## Architecture\nHere is a bird eye's view at the architecture\n\n![MVC Architecture](https://raw.githubusercontent.com/pwpf/demo/master/docs/assets/mvc-architecture.png)\n\n## Installation\n\nThe Boilerplate can be installed directly into your plugins folder \"as-is\". You will want to rename it and the classes inside of it to fit your needs. For example, if your plugin is named 'example-me' then:\n\n* rename files from `plugin-name` to `example-me`\n* change `plugin_name` to `example_me`\n* change `plugin-name` to `example-me`\n* change `Plugin_Name` to `Example_Me`\n* change `PLUGIN_NAME_` to `EXAMPLE_ME_`\n\nIt's safe to activate the plugin at this point. Because the Boilerplate has no real functionality there will be no menu items, meta boxes, or custom post types added until you write the code.\n\n## Getting Started\n\nWe'll try to create a shortcode that prints 10 posts that will help you understand how this boilerplate works. The guide assumes that you have gone through Installation steps and created `Example Me` Plugin.\n\n### 1. Writing your first Router 📡\nRoutes can be defined inside `routes.php` file. Here is how a route can be defined for our example\n```php\n\n// Full Class Name with Namespace\n$router\n    -\u003eregisterRouteOfType( RouteType::FRONTEND )\n    -\u003ewithController( 'Example_Me\\App\\Controllers\\Frontend\\Print_Posts_Shortcode@registerShortcode' )\n    -\u003ewithModel( 'Example_Me\\App\\Models\\Frontend\\Print_Posts_Shortcode' );\n\n// ------------- OR --------------------\n\n// Class Names Without specifying Namespaces explicitly. Boilerplate will automatically figure out the class based on the Route Type.\n$router\n    -\u003eregisterRouteOfType( RouteType::FRONTEND )\n    -\u003ewithController( 'Print_Posts_Shortcode@registerShortcode' )\n    -\u003ewithModel( 'Print_Posts_Shortcode' );\n\n```\n\u003e It is highly recommended to go through [`routes.php`](https://github.com/sumitpore/wordpress-mvc-plugin-boilerplate/blob/master/plugin-name/routes.php). You will get to know list of all available route types \u0026 examples in that file.\n\n\n### 2. Writing your first Controller 🎮\nThe boilerplate converts Class Name to a file name \u0026 loads that file automatically. \n\nWe have passed `Example_Me\\App\\Controllers\\Frontend\\Print_Posts_Shortcode` as a controller in our `routes.php`. Boilerplate resolves this class name to file `example-me/app/controllers/frontend/class-print-posts-shortcode.php`\n\nAny controller that is a part of a Routing (Read: added in `routes.php`) __MUST__ extend `Base_Controller` class. \n* If it is Dashboard (admin) related controller, then it should extend\n`Plugin_Name\\App\\Controllers\\Admin\\Base_Controller`.\n* If it is Frontend related controller, then it should extend\n`Plugin_Name\\App\\Controllers\\Frontend\\Base_Controller`.\n\nEvery controller that extends `Base_Controller` __MUST__ have `register_hook_callbacks`\nmethod. This method is defined as `abstract` in `Base_Controller`.\n\n`register_hook_callbacks` method register callbacks for actions and filters. Most of your add_action/add_filter will go into this method.\n\n`register_hook_callbacks` method is not called automatically. You\nas a developer have to call this method where you see fit. For Example,\nYou may want to call this method in the route itself with `@` , if you feel hooks/filters\ncallbacks should be registered when the new instance of the class\nis created. This way, we don't have to pollute constructor with add_action \u0026 add_filter.\n\nThe purpose of this method is to set the convention that first place to\nfind add_action/add_filter is register_hook_callbacks method.\n\n\u003e NOTE: If you create a constructor inside a controller extending `Base_Controller`, then make sure you call `init` method inside that constructor. That means your custom constructors need to have this line `$this-\u003einit( $model, $view );` to set `Model` \u0026 `View` for your controller object.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eSHOW CONTROLLER EXAMPLE CODE\u003c/b\u003e\u003c/summary\u003e\n    Here is how this file would look for our example\n\n```php\n\u003c?php\n// file: example-me/app/controllers/frontend/class-print-posts-shortcode.php\n\nnamespace Example_Me\\App\\Controllers\\Frontend;\n\nif ( ! class_exists( __NAMESPACE__ . '\\\\' . 'Print_Posts_Shortcode' ) ) {\n    /**\n     * Class that handles `example_me_print_posts` shortcode\n     *\n     * @since      1.0.0\n     * @package    Example_Me\n     * @subpackage Example_Me/Controllers/Frontend\n     */\n    class Print_Posts_Shortcode extends Base_Controller {\n\n        /**\n         * Registers the `example_me_print_posts` shortcode\n         *\n         * @return void\n         * @since 1.0.0\n         */\n        public function registerShortcode() {\n            add_shortcode( 'example_me_print_posts', array( $this, 'print_posts_callback' ) );\n        }\n\n        /**\n         * @ignore Blank Method\n         */\n        protected function register_hook_callbacks(){}\n\n        /**\n         * Callback to handle `example_me_print_posts` shortcode\n         *\n         * @return void\n         * @since 1.0.0\n         */\n        public function print_posts_callback( $atts ) {\n            return     $this-\u003eget_view()-\u003erender_template(\n                'frontend/print-posts-shortcode.php',\n                [\n                    'fetched_posts'    =\u003e    $this-\u003eget_model()-\u003eget_posts_for_shortcode( 'example_me_print_posts', $atts )\n                ]\n            );\n\n        }\n\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n### 3. Writing your first Model ![DNA](https://raw.githubusercontent.com/sumitpore/repo-assets/master/DNA.png)\n\nAll models should extend `AbstractModel` class.\n\n* If it is Dashboard (admin) related model, then it should extend\n`Plugin_Name\\App\\Models\\Admin\\AbstractAdminModel`.\n* If it is Frontend related model, then it should extend\n`Plugin_Name\\App\\Models\\Frontend\\AbstractFrontendModel`.\n\nYou may decide whether to create `register_hook_callbacks` method inside your model or not. It is not an abstract method in `Base_Model` If you want to write any add_action/add_filter, then that should ideally go inside this method. (I would suggest to place all add_action \u0026 add_filter calls inside `register_hook_callbacks` of the Controller class. It will show you all add_actions \u0026 add_filter in one glance but decision is yours! You should do what fits right in your situation.)\n\nAgain `register_hook_callbacks` is not called automatically. If you feel hooks/filters\ncallbacks should be registered when the new instance of the class\nis created, then call this method inside the constructor of your model.\n\n`@` is NOT supported in `withModel` method of Router class, however, `@` is supported in `with_just_model` method of the Router class. If you are confused, what these statements mean? Go through [`routes.php`](https://github.com/sumitpore/wordpress-mvc-plugin-boilerplate/blob/master/plugin-name/routes.php). It contains variety of examples.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eSHOW MODEL EXAMPLE CODE\u003c/b\u003e\u003c/summary\u003e\n\nCreate a file `example-me/app/models/frontend/class-print-posts-shortcode.php` because we have to create `Example_Me\\App\\Models\\Frontend\\Print_Posts_Shortcode` class.\n\nHere is how this file would look for our example\n\n```php\n\u003c?php\n// file: `example-me/app/models/frontend/class-print-posts-shortcode.php`\n\nnamespace Example_Me\\App\\Models\\Frontend;\n\nif ( ! class_exists( __NAMESPACE__ . '\\\\' . 'Print_Posts_Shortcode' ) ) {\n    /**\n     * Class to handle data related operations of `example_me_print_posts` shortcode\n     *\n     * @since      1.0.0\n     * @package    Example_Me\n     * @subpackage Example_Me/Models/Frontend\n     */\n    class Print_Posts_Shortcode extends Example_Me\\App\\Models\\AbstratModel {\n        /**\n         * Fetches posts from database\n         *\n         * @param string $shortcode Shortcode for which posts should be fetched\n         * @param array $atts Arguments passed to shortcode\n         * @return \\WP_Query WP_Query Object\n         */\n        public function get_posts_for_shortcode( $shortcode, $atts ) {\n            $atts = shortcode_atts(\n                array(\n                    'number_of_posts' =\u003e '10',\n                ), $atts, $shortcode\n            );\n\n            $args = array(\n                'post_type' =\u003e 'post',\n                'posts_per_page' =\u003e is_int( $atts['number_of_posts'] ) ? $atts['number_of_posts'] : 10,\n            );\n\n            return new \\WP_Query( $args );\n        }\n    }\n}\n\n```\n\u003c/details\u003e\n\n### 4. Writing a View 👸\n\nIn app/controllers/AbstractController.php you must defined your View config\n\n```php\n\u003c?php \n\n    /**  */\n\n    public function __construct(Model $model, $view = false)\n    {\n        $config = [\n            'appName' =\u003e 'Plugin_Name'\n        ];\n\n        $view = new View($config);\n        parent::__construct($model, $view);\n    }\n\n```\n\u003c/details\u003e\n\n### 5. Writing your first template 👶\nTemplates are the actual files which generate html for the module you are writing. \n\nA template file can be called by invoking `render_template` method on any `View` class's (parent as well as child) object.\n\nTemplate files are created inside `app/templates/` folder.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003cb\u003eSHOW TEMPLATE EXAMPLE CODE\u003c/b\u003e\u003c/summary\u003e\n\nSo the complete location of template file in our example is `example-me/app/templates/frontend/print-posts-shortcode.php`\n\nThis is how it would look\n\n```php\n\n// file: `example-me/app/templates/frontend/print-posts-shortcode.php`\n\n\u003c?php if ( $fetched_posts-\u003ehave_posts() ) : ?\u003e\n\n    \u003c!-- the loop --\u003e\n    \u003c?php\n    while ( $fetched_posts-\u003ehave_posts() ) : ?\u003e\n        \u003c?php $fetched_posts-\u003ethe_post(); ?\u003e\n        \u003ch2\u003e\u003c?php the_title(); ?\u003e\u003c/h2\u003e\n    \u003c?php endwhile; ?\u003e\n    \u003c!-- end of the loop --\u003e\n\n    \u003c?php wp_reset_postdata(); ?\u003e\n\n\u003c?php else : ?\u003e\n    \u003cp\u003e\u003c?php esc_html_e( 'Sorry, no posts matched your criteria.' ); ?\u003e\u003c/p\u003e\n\u003c?php endif; ?\u003e\n```\n\u003c/details\u003e\n\n### 6. Interacting with Settings ⚙️\nWhile developing the plugin, we sometimes need a way to manually interact with the settings information (Side Note - Settings are saved automatically by WordPress if Settings API is used to create settings page)\n\nReplace `Plugin_Name` with your plugin's namespace in below methods.\n\n`Plugin_Name\\App\\Models\\Settings` provide some helper methods to interact with settings data.\n\n| Method | Description |\n| --- | --- |\n| `Plugin_Name\\App\\Models\\Settings::get_plugin_settings_option_key()` | Returns the option key used in wp_options table to save the settings |\n| `Plugin_Name\\App\\Models\\Settings::get_settings()` | Returns all saved settings |\n| `Plugin_Name\\App\\Models\\Settings::get_setting( $setting_name )` | Returns a value of single setting |\n| `Plugin_Name\\App\\Models\\Settings::delete_settings()` | Deletes All Settings |\n| `Plugin_Name\\App\\Models\\Settings::delete_setting( $setting_name )` | Deletes a particular setting |\n| `Plugin_Name\\App\\Models\\Settings::update_settings()` | Updates All Settings |\n| `Plugin_Name\\App\\Models\\Settings::update_setting( $setting_name )` | Updates an individual setting |\n\n### 7. Activation, Deactivation \u0026 Uninstall Procedures? ✨\nActivation, Deactivation \u0026 Uninstall procedures of your plugin go into `Plugin_Name\\App\\Activator::activate()`, `Plugin_Name\\App\\Deactivator::deactivate()` \u0026 `Plugin_Name\\App\\Uninstaller::uninstall()` methods.\n\n### 8. Folder Structure 📁\n| Folder Name | Description |\n| --- | --- |\n| `app` | Functionality shared between the models, controllers and views resides here. Almost everything you write will go into this folder. |\n| `app/models` | The Model component corresponds to all the data-related logic that the user works with. This can represent either the data that is being transferred between the View and Controller components or any other business logic-related data. ( It represents data objects, such as settings, values stored on the database, etc...) |\n| `app/models/admin` | Represents the admin side of the models.\n| `app/models/frontend` | Represents the frontend side of the models.\n| `app/contollers` | Acts as an interface between Model and View components to process all the business logic and incoming requests, manipulate data using the Model component and interact with the Views to render the final output |\n| `app/contollers/admin` | Represents the admin side of the controllers.\n| `app/contollers/frontend` | Represents the frontend side of the controllers.\n| `app/views` | The View component is used for all the UI logic of. It calls required templates.\n| `app/views/admin` | Calls admin side templates\n| `app/views/frontend` | Calls frontend side templates\n| `app/templates` | Represents html code for the feature\n| `app/templates/admin` | Represents html code for admin side features\n| `app/templates/frontend` | Represents html code for frontend side features \n| `assets` | Stores assets required for plugin\n| `core` | Main MVC Framework\n| `docs` | Represents Docs of the plugin\n| `includes` | Contains main class file of the plugin \u0026 i18n class\n| `languages` | All .po, .pot \u0026 .mo goes here \n\n### INSPIRED?\n\n![Build a Fort](https://raw.githubusercontent.com/sumitpore/repo-assets/master/build-a-fort.gif)\n\nIf you like this approach of development, star this repository!\n\n## Features\n\n* The Boilerplate is based on the [Plugin API](http://codex.wordpress.org/Plugin_API), and [Documentation Standards](http://make.wordpress.org/core/handbook/inline-documentation-standards/php-documentation-standards/).\n* All classes, functions, and variables are documented so that you know what you need to be changed.\n* The project includes a `.pot` file as a starting point for internationalization.\n* Separation of concern between Model, View \u0026 Controller.\n* With the appropriate usage of router, plugin's footprint can be kept low.\n\n## Recommended Tools\n\n### i18n Tools\n\nMVC Plugin Boilerplate for WordPress uses a variable to store the text domain used when internationalizing strings throughout the Boilerplate. To take advantage of this method, there are tools that are recommended for providing correct, translatable files:\n\n* [Poedit](http://www.poedit.net/)\n* [makepot](http://i18n.svn.wordpress.org/tools/trunk/)\n* [i18n](https://github.com/grappler/i18n)\n\nAny of the above tools should provide you with the proper tooling to internationalize the plugin.\n\n\n## Credits\n\nThe `MVC Plugin Boilerplate for WordPress` is built upon the `WordPress Plugin Boilerplate` project forked by [Roger Rodrigo](https://ca.linkedin.com/in/rogerrodrigo). The original `WordPress Plugin Boilerplate` project was started in 2011 by [Tom McFarlin](http://twitter.com/tommcfarlin/) and has since included a number of great contributions. In March of 2015 the project was handed over by Tom to Devin Vinson.\n\nThis `MVC Plugin Boilerplate for WordPress` was developed and is being maintained by [Sumit Pore](https://www.linkedin.com/in/sumitpore/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpwpf%2Fdemo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpwpf%2Fdemo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpwpf%2Fdemo/lists"}