{"id":3264,"date":"2023-05-11T13:09:03","date_gmt":"2023-05-11T13:09:03","guid":{"rendered":"https:\/\/icehrm.com\/explore\/?post_type=docs&#038;p=3264"},"modified":"2023-05-11T14:20:49","modified_gmt":"2023-05-11T14:20:49","slug":"structure-of-an-icehrm-extension","status":"publish","type":"docs","link":"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/","title":{"rendered":"Structure of an IceHrm Extension"},"content":{"rendered":"\n<p>Before diving into development, it&#8217;s vital to understand the structure of the extension. The following image shows a list of directories and files inside the extension. Let&#8217;s go through each one of these.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"514\" height=\"423\" src=\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.28.53.png\" alt=\"\" class=\"wp-image-3259\" srcset=\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.28.53.png 514w, https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.28.53-300x247.png 300w\" sizes=\"(max-width: 514px) 100vw, 514px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">kudos.php (Main extension file with the same name as the extension)<\/h4>\n\n\n\n<p>When IceHrm is loaded, all the extensions inside the icehrm\/extensions directory are also initialized. During the initialization, a file with the same name as the extension will be loaded. In this case, it is <code><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-theme-palette-4-color\">kudos.php<\/mark><\/code>. This file is responsible for loading the rest of the code responsible for initializing the extension.<\/p>\n\n\n\n<p>A closer look at <code>kudos.php<\/code> shows how it is loading different classes required by the extension.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nrequire_once __DIR__.'\/src\/Extension.php';\nrequire_once __DIR__.'\/src\/Controller.php';\nrequire_once __DIR__.'\/src\/ApiController.php';\n\n\/\/ Migrations\nrequire_once __DIR__.'\/src\/Migrations\/CreateTables.php';<\/code><\/pre>\n\n\n\n<p>Each class being loaded here is important for the extension to work properly. Let&#8217;s explore the responsibility of each class.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Extension Manager<\/h4>\n\n\n\n<p>Class <strong>Extension<\/strong> defined in <strong>src\/extension.php<\/strong> is the file that controls the behaviour of the extension. It is responsible for:<\/p>\n\n\n\n<ul>\n<li>Loading database models for the <a href=\"https:\/\/github.com\/thilinah\/php-active-record\" target=\"_blank\" rel=\"noreferrer noopener\">ORM<\/a> used by IceHrm.<\/li>\n\n\n\n<li>Defining REST API.<\/li>\n\n\n\n<li>Loading the Controller.<\/li>\n\n\n\n<li>Loading the dashboard menu for the extension, etc.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Controller<\/h4>\n\n\n\n<p>IceHrm provides a very convenient way to pass data between the front end and backend of the extension. The class Controller defines a set of actions that can be called from the front end.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\nnamespace KudosAdmin;\n\nuse Classes\\IceController;\nuse Classes\\IceResponse;\n\nclass Controller extends IceController\n{\n    public function testAction($req): IceResponse\n    {\n        return new IceResponse(IceResponse::SUCCESS, 'Echo from server: '.$req-&gt;data);\n    }\n}<\/code><\/pre>\n\n\n\n<p>Invoking these actions can be done via <strong>AdminExtensionController<\/strong> class defined in <strong>web\/js\/controller.js<\/strong> file.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>handleTestAction() {\n    \/**\n     * The `testAction` should be defined in the `Controller.php` class\n     *\/\n    this.handleRequest(\n      'testAction',\n      { data: 'message from client' },\n    ).then((response) =&gt; {\n      console.log(response);\n    });\n  }<\/code><\/pre>\n\n\n\n<p>As you may see above the <strong>handleTestAction<\/strong> method is passing some data to the <strong>testAction<\/strong> and prints the output to the console.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Rest API<\/h4>\n\n\n\n<p>A Rest API for the extension can be easily defined and exposed using <strong>src\/ApiController.php<\/strong>. The example methods added in <strong>ApiController<\/strong> with the generated code will further show how it can be utilized.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Database Migration<\/h4>\n\n\n\n<p>The file <strong>src\/Migrations\/CreateTables.php<\/strong> defines a sample migration. You are free to create additional migrations and add them to the <strong>\\KudosAdmin\\Extension::initialize<\/strong> method. <\/p>\n\n\n\n<p>e.g:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public function initialize() {\n    BaseService::getInstance()-&gt;registerExtensionMigration(new CreateTables());\n}<\/code><\/pre>\n\n\n\n<p>Each migration can perform tasks like creating a table or adding data to the database required by the extension. Each migration needs to have a unique name. Here is an example of a migration that creates a table for this extension.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\nnamespace KudosAdmin\\Migrations;\n\nuse Classes\\Migration\\AbstractMigration;\nuse Classes\\Migration\\MigrationInterface;\n\nclass CreateTables extends AbstractMigration implements MigrationInterface\n{\n\t\n\tpublic function getName() {\n\t\treturn 'kudos_create_table';\n\t}\n\t\n\tpublic function up() {\n        $sql = &lt;&lt;&lt;'SQL'\n            create table EmployeeKudos\n            (\n                id bigint auto_increment primary key,\n                employee bigint null,\n                sender bigint not null,\n                message varchar(500) charset utf8 not null,\n                created datetime null,\n                updated datetime null,\n                CONSTRAINT `Fk_EmployeeKudos_Sender` FOREIGN KEY (`sender`) REFERENCES `Employees` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,\n                CONSTRAINT `Fk_EmployeeKudos_Employee` FOREIGN KEY (`employee`) REFERENCES `Employees` (`id`) ON DELETE CASCADE ON UPDATE CASCADE\n            )\n            collate=utf8mb4_unicode_ci;\nSQL;\n        $this-&gt;executeQuery($sql);\n\t}\n\t\n\tpublic function down() {\n        $sql = &lt;&lt;&lt;'SQL'\n            DROP TABLE IF EXISTS `EmployeeKudos`; \nSQL;\n        return $this-&gt;executeQuery($sql);\n\t}\n}\n<\/code><\/pre>\n\n\n\n<p>When the extension is loaded, IceHrm core checks if the migration has already been executed. If it is not executed the migration up method will be used to create the EmployeeKudos table.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Creating an ORM Model For EmployeeKudos Table<\/h4>\n\n\n\n<p>In order to access data using <a href=\"https:\/\/github.com\/thilinah\/php-active-record\">php-active-record<\/a>, you need to create a model class for the table defined in previous sections. These models are defined in <strong>src\/Common\/Model<\/strong> directory and loaded in <code>\\KudosAdmin\\Extension::setupModuleClassDefinitions<\/code>.<\/p>\n\n\n\n<p>Models are not auto-generated. So you will need to add this class manually.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ File: src\/Common\/Model\/EmployeeKudos.php\n&lt;?php\nnamespace KudosAdmin\\Common\\Model;\n\nuse Model\\BaseModel;\n\nclass EmployeeKudos extends BaseModel\n{\n    public $table = 'EmployeeKudos';\n}<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Frontend Structure<\/h4>\n\n\n\n<p>IceHrm extension web directory has the following structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\ud83d\udce6extensions\n \u2523 \ud83d\udcc2web\n   \u2523 \ud83d\udcc2js\n   \u2503 \u2523 \ud83d\udcdccontroller.js\n   \u2503 \u2523 \ud83d\udcdcindex.js\n   \u2523 \u2523 \ud83d\udcdcmodule.js\n   \u2503 \u2517 \ud83d\udcdcview.js\n   \u2517 \ud83d\udcdcindex.php\n<\/code><\/pre>\n\n\n\n<p>The <strong>index.php<\/strong> is the entry point for the front end. It defines a <strong>&lt;div&gt;<\/strong> element to load the root react component. Also, it calls the <strong>init<\/strong> function defined in <strong>web\/js\/index.php<\/strong> to start initializing Module class defined in <strong>web\/js\/module.js<\/strong>. <\/p>\n\n\n\n<p>After IceHrm framework is initialized the <strong>showExtensionView<\/strong> function of the module class will be called. This function mounts the react component which you can see here:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"428\" src=\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05-1024x428.png\" alt=\"\" class=\"wp-image-3260\" srcset=\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05-1024x428.png 1024w, https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05-300x125.png 300w, https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05-768x321.png 768w, https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05-600x251.png 600w, https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.34.05.png 1365w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">empty Kudos Admin extension<\/figcaption><\/figure>\n\n\n\n<p>Your entry point to build the frontend for the extension is <strong>web\/js\/view.js<\/strong> file which contains a React component which will be used as the main container component.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Tech Stack Used for Extensions<\/h3>\n\n\n\n<ul>\n<li>IceHrm extensions use React and <a href=\"https:\/\/ant.design\/\">Ant Design<\/a> to build the front end. <\/li>\n\n\n\n<li>The backend is developed using PHP.<\/li>\n\n\n\n<li>We use the <a href=\"https:\/\/github.com\/thilinah\/php-active-record\" target=\"_blank\" rel=\"noreferrer noopener\">php-active-record<\/a> for the DB layer.<\/li>\n\n\n\n<li>When building frontend components please use Ant Design components to match the UI of the rest of the application. The currently supported <a href=\"https:\/\/4x.ant.design\/\">ant design version is 4.x<\/a>.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Before diving into development, it&#8217;s vital to understand the structure of the extension. The following image shows a list of directories and files inside the extension. Let&#8217;s go through each one of these. kudos.php (Main extension file with the same name as the extension) When IceHrm is loaded, all the extensions inside the icehrm\/extensions directory&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","kt_blocks_editor_width":"","_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"footnotes":""},"doc_category":[34],"doc_tag":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v15.6.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Structure of an IceHrm Extension - IceHrm<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Structure of an IceHrm Extension - IceHrm\" \/>\n<meta property=\"og:description\" content=\"Before diving into development, it&#8217;s vital to understand the structure of the extension. The following image shows a list of directories and files inside the extension. Let&#8217;s go through each one of these. kudos.php (Main extension file with the same name as the extension) When IceHrm is loaded, all the extensions inside the icehrm\/extensions directory...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/\" \/>\n<meta property=\"og:site_name\" content=\"IceHrm\" \/>\n<meta property=\"article:modified_time\" content=\"2023-05-11T14:20:49+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.28.53.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\">\n\t<meta name=\"twitter:data1\" content=\"4 minutes\">\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/icehrm.com\/explore\/#website\",\"url\":\"https:\/\/icehrm.com\/explore\/\",\"name\":\"IceHrm\",\"description\":\"IceHrm Marketplace\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/icehrm.com\/explore\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/icehrm.com\/explore\/wp-content\/uploads\/2023\/05\/Screenshot-2023-05-09-at-09.28.53.png\",\"width\":514,\"height\":423},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/#webpage\",\"url\":\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/\",\"name\":\"Structure of an IceHrm Extension - IceHrm\",\"isPartOf\":{\"@id\":\"https:\/\/icehrm.com\/explore\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/#primaryimage\"},\"datePublished\":\"2023-05-11T13:09:03+00:00\",\"dateModified\":\"2023-05-11T14:20:49+00:00\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/icehrm.com\/explore\/docs\/structure-of-an-icehrm-extension\/\"]}]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/docs\/3264"}],"collection":[{"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/docs"}],"about":[{"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/types\/docs"}],"author":[{"embeddable":true,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/comments?post=3264"}],"version-history":[{"count":4,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/docs\/3264\/revisions"}],"predecessor-version":[{"id":3273,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/docs\/3264\/revisions\/3273"}],"wp:attachment":[{"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/media?parent=3264"}],"wp:term":[{"taxonomy":"doc_category","embeddable":true,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/doc_category?post=3264"},{"taxonomy":"doc_tag","embeddable":true,"href":"https:\/\/icehrm.com\/explore\/wp-json\/wp\/v2\/doc_tag?post=3264"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}