diff --git a/404.html b/404.html index 968e91bff..d37991a40 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | Apiato - - + +
-
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+ + \ No newline at end of file diff --git a/assets/js/023af9ff.07d4fc1e.js b/assets/js/023af9ff.07d4fc1e.js deleted file mode 100644 index eb4519e26..000000000 --- a/assets/js/023af9ff.07d4fc1e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8866],{3905:(e,r,i)=>{i.d(r,{Zo:()=>d,kt:()=>m});var n=i(67294);function t(e,r,i){return r in e?Object.defineProperty(e,r,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[r]=i,e}function a(e,r){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var r=1;r=0||(t[i]=e[i]);return t}(e,r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(t[i]=e[i])}return t}var s=n.createContext({}),l=function(e){var r=n.useContext(s),i=r;return e&&(i="function"==typeof e?e(r):o(o({},r),e)),i},d=function(e){var r=l(e.components);return n.createElement(s.Provider,{value:r},e.children)},v="mdxType",c={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},u=n.forwardRef((function(e,r){var i=e.components,t=e.mdxType,a=e.originalType,s=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),v=l(i),u=t,m=v["".concat(s,".").concat(u)]||v[u]||c[u]||a;return i?n.createElement(m,o(o({ref:r},d),{},{components:i})):n.createElement(m,o({ref:r},d))}));function m(e,r){var i=arguments,t=r&&r.mdxType;if("string"==typeof e||t){var a=i.length,o=new Array(a);o[0]=u;var p={};for(var s in r)hasOwnProperty.call(r,s)&&(p[s]=r[s]);p.originalType=e,p[v]="string"==typeof e?e:t,o[1]=p;for(var l=2;l{i.r(r),i.d(r,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var n=i(87462),t=(i(67294),i(3905));const a={title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},o=void 0,p={unversionedId:"components/optional-components/service-providers",id:"version-12.x/components/optional-components/service-providers",title:"Service Providers",description:"Apiato service providers are just Laravel Service Providers,",source:"@site/versioned_docs/version-12.x/components/optional-components/service-providers.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/service-providers",permalink:"/docs/components/optional-components/service-providers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/service-providers.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"service-provider",permalink:"/docs/tags/service-provider"},{label:"middleware",permalink:"/docs/tags/middleware"},{label:"event",permalink:"/docs/tags/event"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},sidebar:"tutorialSidebar",previous:{title:"Seeders",permalink:"/docs/components/optional-components/seeders"},next:{title:"Tests",permalink:"/docs/components/optional-components/tests"}},s={},l=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Main Service Provider:",id:"main-service-provider",level:4},{value:"Register Providers",id:"register-providers",level:2},{value:"Container Service Providers",id:"container-service-providers",level:3},{value:"Main Service Provider",id:"main-service-provider-1",level:4},{value:"Additional Service Providers",id:"additional-service-providers",level:4},{value:"General Service Providers",id:"general-service-providers",level:3},{value:"Third Party Service Providers",id:"third-party-service-providers",level:3},{value:"Laravel Service Providers",id:"laravel-service-providers",level:2},{value:"Service Providers Registration Flow",id:"service-providers-registration-flow",level:2}],d={toc:l},v="wrapper";function c(e){let{components:r,...i}=e;return(0,t.kt)(v,(0,n.Z)({},d,i,{components:r,mdxType:"MDXLayout"}),(0,t.kt)("p",null,"Apiato service providers are just ",(0,t.kt)("a",{parentName:"p",href:"https://laravel.com/docs/providers"},"Laravel Service Providers"),",\nand they function in the exact same way as Laravel service providers.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.kt)("p",null,"To generate new service providers\nyou may use the ",(0,t.kt)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.kt)("p",null,"There are two distinct types of service providers within a container:\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," and additional service providers.\nThe Main Service Provider serves as the central registration point for all custom service providers within the container.\nIt orchestrates the setup and integration of these custom providers,\nensuring the seamless functioning of your application's components."),(0,t.kt)("h2",{id:"rules"},"Rules"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},"You MUST NOT register any Service Provider in the ",(0,t.kt)("inlineCode",{parentName:"li"},"config/app.php")," (except Laravel default service providers)."),(0,t.kt)("li",{parentName:"ul"},"Each Container:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MAY have one or many Service Providers."),(0,t.kt)("li",{parentName:"ul"},"MUST have a ",(0,t.kt)("inlineCode",{parentName:"li"},"Main Service Provider")," -> ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be named ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))))))),(0,t.kt)("li",{parentName:"ul"},"All container-specific Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in their respective container's ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All general Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Ship/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Prviders\\ShipProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All non-Laravel or third-party package Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))),(0,t.kt)("li",{parentName:"ul"},"When using Laravel ",(0,t.kt)("a",{parentName:"li",href:"#laravel-service-providers"},"default service providers"),":",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"AuthServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"BroadcastServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"EventServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MiddlewareServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MiddlewareServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"RouteServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"Parent{ServiceProviderName}"),". For example: ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentAuthServiceProvider"),".")))),(0,t.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.kt)("p",null,"The highlighted sections showcase service provider registration points:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider.php")," acts as the central registration point for custom service providers specific to a container."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"ShipProvider.php")," acts as the central registration point for the Ship (general) service providers.")),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AuthServiceProvider.php\n\u2502 \u251c\u2500\u2500 BroadcastServiceProvider.php\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php\n // highlight-start\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php\n // highlight-end\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php\n\u2502 \u251c\u2500\u2500 RouteServiceProvider.php\n\u2502 \u251c\u2500\u2500 CustomServiceProvider.php\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 RouteServiceProvider.php\n // highlight-start\n \u251c\u2500\u2500 ShipProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("h2",{id:"code-example"},"Code Example"),(0,t.kt)("h4",{id:"main-service-provider"},"Main Service Provider:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n // This providers will be automatically registered\n public array $serviceProviders = [\n CustomServiceProvider::class,\n MiddlewareServiceProvider::class,\n PassportServiceProvider::class,\n // ...\n ];\n\n public array $aliases = [\n // ...\n ];\n}\n")),(0,t.kt)("h2",{id:"register-providers"},"Register Providers"),(0,t.kt)("p",null,"The registration process for a service provider varies depending on its intended scope within the application.\nDifferent places are designated for different levels of service provider usage."),(0,t.kt)("p",null,"In essence, the decision of where to register a service provider boils down to two key factors:\nthe scope of service provider usage and the logical location for its registration."),(0,t.kt)("h3",{id:"container-service-providers"},"Container Service Providers"),(0,t.kt)("h4",{id:"main-service-provider-1"},"Main Service Provider"),(0,t.kt)("p",null,"A container ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," will be automatically registered by Apiato\nso manual registration isn't necessary.\nIn turn,\nMain Service Providers will register all service providers listed in their ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," property."),(0,t.kt)("h4",{id:"additional-service-providers"},"Additional Service Providers"),(0,t.kt)("p",null,"To register a provider,\nadd the provider's class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("p",null,"You can also list aliases in the ",(0,t.kt)("inlineCode",{parentName:"p"},"aliases")," property of the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $aliases = [\n 'CustomAlias' => CustomFacade::class,\n 'AnotherCustomAlias' => AnotherCustomFacade::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"general-service-providers"},"General Service Providers"),(0,t.kt)("p",null,"General service providers must be registered in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Providers\\ShipProvider")," class.\nThis can be done by adding the provider class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"third-party-service-providers"},"Third Party Service Providers"),(0,t.kt)("p",null,"When dealing with third-party packages that require service provider registration in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),",\nyou should follow these guidelines:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Specific Container Usage"),": If the package is used within a particular container, register its service provider in that container ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Framework-wide Usage"),": If the package is generic and used throughout the entire application, you can register its service provider in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," class. However, avoid registering it directly in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),"."))),(0,t.kt)("h2",{id:"laravel-service-providers"},"Laravel Service Providers"),(0,t.kt)("p",null,"Apiato introduces a refined organization for Laravel service providers.\nBy default, Laravel standard service providers,\nlocated in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/providers")," directory,\nhave been restructured in Apiato to reside in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/Ship/Parents/Providers")," directory."),(0,t.kt)("p",null,"Here's the mapping of Laravel's default service providers to their new locations in Apiato:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AppServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider"),(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"Note: Laravel ",(0,t.kt)("inlineCode",{parentName:"li"},"AppServiceProvider")," is renamed to ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider")," in Apiato."))),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AuthServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\BroadcastServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\EventServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\RouteServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"))),(0,t.kt)("p",null,"You should not modify these providers directly.\nInstead, extend them within your container's ",(0,t.kt)("inlineCode",{parentName:"p"},"Providers")," directory.\nFor instance,\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\Authentication\\Providers\\AuthServiceProvider")," class extends ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("p",null,"Those providers are not auto registered by default,\nthus writing any code there will not be available, unless you extend them.\nOnce extended, the child Service Provider should be registered in its container ",(0,t.kt)("inlineCode",{parentName:"p"},"MainServiceProvider"),",\nwhich makes it available."),(0,t.kt)("admonition",{type:"note"},(0,t.kt)("p",{parentName:"admonition"},"Do note that the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider")," is a unique case.\nBecause it's required by Apiato, it is registered by the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," and is loaded automatically.")),(0,t.kt)("h2",{id:"service-providers-registration-flow"},"Service Providers Registration Flow"),(0,t.kt)("p",null,"If you want to understand the service provider registration process,\nhere is a breakdown of the registration flow."),(0,t.kt)("p",null,"Consider the following folder structure:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u251c\u2500\u2500 ContainerA\n\u2502 \u2502 \u2514\u2500\u2500 Providers\n\u2502 \u2502 \u251c\u2500\u2500 CustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 ContainerB\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AnotherCustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2524\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 CustomGeneralServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 RouteServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u251c\u2500\u2500 ShipProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("p",null,"The following diagram illustrates the registration flow of service providers in the above folder structure:"),(0,t.kt)("mermaid",{value:"graph TB\n subgraph ContainerB[Container B]\n BMP[MainServiceProvider]\n BEP[EventServiceProvider]\n subgraph BServiceProviders[Service Providers]\n AnotherCustomServiceProvider\n BEP\n MiddlewareServiceProvider\n end\n BMP\n end\n \n subgraph ContainerA[Container A]\n AMP[MainServiceProvider]\n subgraph AServiceProviders[Service Providers]\n CustomServiceProvider\n EventServiceProvider\n end\n AMP\n end\n \n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eAMP\n SPLoader-- loads--\x3eBMP\n end\n \n AMP --\x3e|loads| AServiceProviders\n AServiceProviders --\x3e|registered in| AMP\n BMP --\x3e|loads| BServiceProviders\n BServiceProviders --\x3e|registered in| BMP"}))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/023af9ff.d559b940.js b/assets/js/023af9ff.d559b940.js new file mode 100644 index 000000000..92a239543 --- /dev/null +++ b/assets/js/023af9ff.d559b940.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8866],{3905:(e,r,i)=>{i.d(r,{Zo:()=>d,kt:()=>m});var n=i(67294);function t(e,r,i){return r in e?Object.defineProperty(e,r,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[r]=i,e}function a(e,r){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var r=1;r=0||(t[i]=e[i]);return t}(e,r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(t[i]=e[i])}return t}var s=n.createContext({}),l=function(e){var r=n.useContext(s),i=r;return e&&(i="function"==typeof e?e(r):o(o({},r),e)),i},d=function(e){var r=l(e.components);return n.createElement(s.Provider,{value:r},e.children)},v="mdxType",c={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},u=n.forwardRef((function(e,r){var i=e.components,t=e.mdxType,a=e.originalType,s=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),v=l(i),u=t,m=v["".concat(s,".").concat(u)]||v[u]||c[u]||a;return i?n.createElement(m,o(o({ref:r},d),{},{components:i})):n.createElement(m,o({ref:r},d))}));function m(e,r){var i=arguments,t=r&&r.mdxType;if("string"==typeof e||t){var a=i.length,o=new Array(a);o[0]=u;var p={};for(var s in r)hasOwnProperty.call(r,s)&&(p[s]=r[s]);p.originalType=e,p[v]="string"==typeof e?e:t,o[1]=p;for(var l=2;l{i.r(r),i.d(r,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var n=i(87462),t=(i(67294),i(3905));const a={title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},o=void 0,p={unversionedId:"components/optional-components/service-providers",id:"version-12.x/components/optional-components/service-providers",title:"Service Providers",description:"Apiato service providers are just Laravel Service Providers,",source:"@site/versioned_docs/version-12.x/components/optional-components/service-providers.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/service-providers",permalink:"/docs/components/optional-components/service-providers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/service-providers.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"service-provider",permalink:"/docs/tags/service-provider"},{label:"middleware",permalink:"/docs/tags/middleware"},{label:"event",permalink:"/docs/tags/event"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",frontMatter:{title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},sidebar:"tutorialSidebar",previous:{title:"Seeders",permalink:"/docs/components/optional-components/seeders"},next:{title:"Tests",permalink:"/docs/components/optional-components/tests"}},s={},l=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Main Service Provider:",id:"main-service-provider",level:4},{value:"Register Providers",id:"register-providers",level:2},{value:"Container Service Providers",id:"container-service-providers",level:3},{value:"Main Service Provider",id:"main-service-provider-1",level:4},{value:"Additional Service Providers",id:"additional-service-providers",level:4},{value:"General Service Providers",id:"general-service-providers",level:3},{value:"Third Party Service Providers",id:"third-party-service-providers",level:3},{value:"Laravel Service Providers",id:"laravel-service-providers",level:2},{value:"Service Providers Registration Flow",id:"service-providers-registration-flow",level:2}],d={toc:l},v="wrapper";function c(e){let{components:r,...i}=e;return(0,t.kt)(v,(0,n.Z)({},d,i,{components:r,mdxType:"MDXLayout"}),(0,t.kt)("p",null,"Apiato service providers are just ",(0,t.kt)("a",{parentName:"p",href:"https://laravel.com/docs/providers"},"Laravel Service Providers"),",\nand they function in the exact same way as Laravel service providers.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.kt)("p",null,"To generate new service providers\nyou may use the ",(0,t.kt)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.kt)("p",null,"There are two distinct types of service providers within a container:\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," and additional service providers.\nThe Main Service Provider serves as the central registration point for all custom service providers within the container.\nIt orchestrates the setup and integration of these custom providers,\nensuring the seamless functioning of your application's components."),(0,t.kt)("h2",{id:"rules"},"Rules"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},"You MUST NOT register any Service Provider in the ",(0,t.kt)("inlineCode",{parentName:"li"},"config/app.php")," (except Laravel default service providers)."),(0,t.kt)("li",{parentName:"ul"},"Each Container:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MAY have one or many Service Providers."),(0,t.kt)("li",{parentName:"ul"},"MUST have a ",(0,t.kt)("inlineCode",{parentName:"li"},"Main Service Provider")," -> ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be named ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))))))),(0,t.kt)("li",{parentName:"ul"},"All container-specific Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in their respective container's ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All general Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Ship/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Prviders\\ShipProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All non-Laravel or third-party package Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))),(0,t.kt)("li",{parentName:"ul"},"When using Laravel ",(0,t.kt)("a",{parentName:"li",href:"#laravel-service-providers"},"default service providers"),":",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"AuthServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"BroadcastServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"EventServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MiddlewareServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MiddlewareServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"RouteServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"Parent{ServiceProviderName}"),". For example: ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentAuthServiceProvider"),".")))),(0,t.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.kt)("p",null,"The highlighted sections showcase service provider registration points:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider.php")," acts as the central registration point for custom service providers specific to a container."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"ShipProvider.php")," acts as the central registration point for the Ship (general) service providers.")),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AuthServiceProvider.php\n\u2502 \u251c\u2500\u2500 BroadcastServiceProvider.php\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php\n // highlight-start\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php\n // highlight-end\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php\n\u2502 \u251c\u2500\u2500 RouteServiceProvider.php\n\u2502 \u251c\u2500\u2500 CustomServiceProvider.php\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 RouteServiceProvider.php\n // highlight-start\n \u251c\u2500\u2500 ShipProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("h2",{id:"code-example"},"Code Example"),(0,t.kt)("h4",{id:"main-service-provider"},"Main Service Provider:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n // This providers will be automatically registered\n public array $serviceProviders = [\n CustomServiceProvider::class,\n MiddlewareServiceProvider::class,\n PassportServiceProvider::class,\n // ...\n ];\n\n public array $aliases = [\n // ...\n ];\n}\n")),(0,t.kt)("h2",{id:"register-providers"},"Register Providers"),(0,t.kt)("p",null,"The registration process for a service provider varies depending on its intended scope within the application.\nDifferent places are designated for different levels of service provider usage."),(0,t.kt)("p",null,"In essence, the decision of where to register a service provider boils down to two key factors:\nthe scope of service provider usage and the logical location for its registration."),(0,t.kt)("h3",{id:"container-service-providers"},"Container Service Providers"),(0,t.kt)("h4",{id:"main-service-provider-1"},"Main Service Provider"),(0,t.kt)("p",null,"A container ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," will be automatically registered by Apiato\nso manual registration isn't necessary.\nIn turn,\nMain Service Providers will register all service providers listed in their ",(0,t.kt)("inlineCode",{parentName:"p"},"$serviceProviders")," property."),(0,t.kt)("h4",{id:"additional-service-providers"},"Additional Service Providers"),(0,t.kt)("p",null,"To register a provider,\nadd the provider's class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("p",null,"You can also list aliases in the ",(0,t.kt)("inlineCode",{parentName:"p"},"$aliases")," property of the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $aliases = [\n 'CustomAlias' => CustomFacade::class,\n 'AnotherCustomAlias' => AnotherCustomFacade::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"general-service-providers"},"General Service Providers"),(0,t.kt)("p",null,"General service providers must be registered in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Providers\\ShipProvider")," class.\nThis can be done by adding the provider class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"third-party-service-providers"},"Third Party Service Providers"),(0,t.kt)("p",null,"When dealing with third-party packages that require service provider registration in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),",\nyou should follow these guidelines:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Specific Container Usage"),": If the package is used within a particular container, register its service provider in that container ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Framework-wide Usage"),": If the package is generic and used throughout the entire application, you can register its service provider in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," class. However, avoid registering it directly in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),"."))),(0,t.kt)("h2",{id:"laravel-service-providers"},"Laravel Service Providers"),(0,t.kt)("p",null,"Apiato introduces a refined organization for Laravel service providers.\nBy default, Laravel standard service providers,\nlocated in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/providers")," directory,\nhave been restructured in Apiato to reside in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/Ship/Parents/Providers")," directory."),(0,t.kt)("p",null,"Here's the mapping of Laravel's default service providers to their new locations in Apiato:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AppServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider"),(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"Note: Laravel ",(0,t.kt)("inlineCode",{parentName:"li"},"AppServiceProvider")," is renamed to ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider")," in Apiato."))),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AuthServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\BroadcastServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\EventServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\RouteServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"))),(0,t.kt)("p",null,"You should not modify these providers directly.\nInstead, extend them within your container's ",(0,t.kt)("inlineCode",{parentName:"p"},"Providers")," directory.\nFor instance,\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\Authentication\\Providers\\AuthServiceProvider")," class extends ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("p",null,"Those providers are not auto registered by default,\nthus writing any code there will not be available, unless you extend them.\nOnce extended, the child Service Provider should be registered in its container ",(0,t.kt)("inlineCode",{parentName:"p"},"MainServiceProvider"),",\nwhich makes it available."),(0,t.kt)("admonition",{type:"note"},(0,t.kt)("p",{parentName:"admonition"},"Do note that the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider")," is a unique case.\nBecause it's required by Apiato, it is registered by the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," and is loaded automatically.")),(0,t.kt)("h2",{id:"service-providers-registration-flow"},"Service Providers Registration Flow"),(0,t.kt)("p",null,"If you want to understand the service provider registration process,\nhere is a breakdown of the registration flow."),(0,t.kt)("p",null,"Consider the following folder structure:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u251c\u2500\u2500 ContainerA\n\u2502 \u2502 \u2514\u2500\u2500 Providers\n\u2502 \u2502 \u251c\u2500\u2500 CustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 ContainerB\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AnotherCustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2524\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 CustomGeneralServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 RouteServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u251c\u2500\u2500 ShipProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("p",null,"The following diagram illustrates the registration flow of service providers in the above folder structure:"),(0,t.kt)("mermaid",{value:"graph TB\n subgraph ContainerB[Container B]\n BMP[MainServiceProvider]\n BEP[EventServiceProvider]\n subgraph BServiceProviders[Service Providers]\n AnotherCustomServiceProvider\n BEP\n MiddlewareServiceProvider\n end\n BMP\n end\n \n subgraph ContainerA[Container A]\n AMP[MainServiceProvider]\n subgraph AServiceProviders[Service Providers]\n CustomServiceProvider\n EventServiceProvider\n end\n AMP\n end\n \n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eAMP\n SPLoader-- loads--\x3eBMP\n end\n \n AMP --\x3e|loads| AServiceProviders\n AServiceProviders --\x3e|registered in| AMP\n BMP --\x3e|loads| BServiceProviders\n BServiceProviders --\x3e|registered in| BMP"}))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1db39f58.4e7a3e77.js b/assets/js/1db39f58.4e7a3e77.js new file mode 100644 index 000000000..2c22f4733 --- /dev/null +++ b/assets/js/1db39f58.4e7a3e77.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[3022],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>m});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,s=e.originalType,l=e.parentName,d=r(e,["components","mdxType","originalType","parentName"]),h=p(n),c=i,m=h["".concat(l,".").concat(c)]||h[c]||u[c]||s;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,o=new Array(s);o[0]=c;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[h]="string"==typeof e?e:i,o[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const s={title:"Tests",tags:["component","optional-component","test"]},o=void 0,r={unversionedId:"components/optional-components/tests",id:"version-12.x/components/optional-components/tests",title:"Tests",description:"Apiato is built with testing in mind.",source:"@site/versioned_docs/version-12.x/components/optional-components/tests.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/tests",permalink:"/docs/components/optional-components/tests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/tests.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"test",permalink:"/docs/tags/test"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",frontMatter:{title:"Tests",tags:["component","optional-component","test"]},sidebar:"tutorialSidebar",previous:{title:"Service Providers",permalink:"/docs/components/optional-components/service-providers"},next:{title:"Values",permalink:"/docs/components/optional-components/values"}},l={},p=[{value:"Definitions",id:"definitions",level:2},{value:"Unit tests",id:"unit-tests",level:4},{value:"Functional tests",id:"functional-tests",level:4},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Writing Tests",id:"writing-tests",level:2},{value:"Functional Test Helpers",id:"functional-test-helpers",level:2},{value:"Properties",id:"properties",level:3},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"access",id:"access",level:4},{value:"Methods",id:"methods",level:3},{value:"makeCall",id:"makecall",level:4},{value:"injectId",id:"injectid",level:4},{value:"getTestingUser",id:"gettestinguser",level:4},{value:"getTestingUserWithoutAccess",id:"gettestinguserwithoutaccess",level:4},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"Available Assertions",id:"available-assertions",level:2},{value:"assertModelCastsIsEmpty",id:"assertmodelcastsisempty",level:4},{value:"assertDatabaseTable",id:"assertdatabasetable",level:4},{value:"getGateMock",id:"getgatemock",level:4},{value:"inIds",id:"inids",level:4},{value:"Faker",id:"faker",level:2},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},h="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(h,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato is built with testing in mind.\nIn fact,\nsupport for testing with PHPUnit is included out of the box\nand a ",(0,i.kt)("inlineCode",{parentName:"p"},"phpunit.xml")," file is already set up for your application.\nIn addition to the testing capabilities provided by Laravel,\nApiato enhances the testing experience by including convenient helper methods.\nThese methods enable you to write expressive tests for your applications, further enhancing the testing process.\nYou can refer to Laravel documentation on ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.kt)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.kt)("h2",{id:"definitions"},"Definitions"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"unit-tests"},"Unit tests"),(0,i.kt)("p",null,"Unit tests are tests that focus on a very small, isolated portion of your code.\nIn fact, most unit tests probably focus on a single method."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"functional-tests"},"Functional tests"),(0,i.kt)("p",null,"Functional tests may test a larger portion of your code,\nincluding how several objects interact with each other or even a full HTTP request to a JSON endpoint.\nGenerally, most of your tests should be functional tests.\nThese types of tests provide the most confidence that your system as a whole is functioning as intended."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All container-specific Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All ",(0,i.kt)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All API Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All WEB Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All TestCases MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class. e.g.:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")),(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u251c\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTaskTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UnitTestCase.php\n\u2502 \u2514\u2500\u2500 UI\n\u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2514\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 ApiTestCase.php\n\u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 LoginTest.php\n\u2502 \u2502 \u251c\u2500\u2500 LogoutTest.php\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 WebTestCase.php\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Tests\n \u251c\u2500\u2500 Unit\n \u2502 \u251c\u2500\u2500 UrlRuleTest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 TestCase.php\n")),(0,i.kt)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.kt)("p",null,"Unit tests are defined in the same manner as you would define them in Laravel.\nHowever, Functional tests follow a distinct approach.\nHere's an example of how to write functional tests:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\UI\\API\\Tests\\Functional;\n\nuse App\\Containers\\AppSection\\User\\UI\\API\\Tests\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\n/**\n * @group user\n * @group api\n */\nclass FindUserByIdTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users/{id}';\n protected bool $auth = true;\n protected array $access = [\n 'permissions' => 'search-users',\n 'roles' => '',\n ];\n\n public function testFindUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertOk();\n $response->assertJson(\n static fn (AssertableJson $json) => $json->has('data')\n ->where('data.id', \\Hashids::encode($user->id))\n ->etc()\n );\n }\n}\n")),(0,i.kt)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.kt)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.kt)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.kt)("h3",{id:"properties"},"Properties"),(0,i.kt)("p",null,"Certain test helper methods access properties defined in your test class to execute their tasks effectively.\nBelow, we will explore these properties and their corresponding methods:"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.kt)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class FindUserByIdTest extends ApiTestCase\n{\n // highlight-start\n protected string $endpoint = 'get@v1/profile';\n // highlight-end\n \n public function testGetAuthenticatedUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->makeCall();\n // You can override the \"endpoint\" property in specific test methods\n // $response = $this->endpoint('get@v1/users')->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property is used to determine whether the endpoint being called requires authentication or not in your test class.\nIf you do not explicitly define the ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.kt)("p",null,"In the context of testing, when ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method will handle authentication by creating a testing user\n(if one is not already available) and injecting their access token into the headers before making the API call."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class ListUsersTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users';\n // highlight-start\n protected bool $auth = false;\n // highlight-end\n \n public function testListUsers(): void\n {\n $response = $this->makeCall();\n // You can override the \"auth\" property in specific test methods\n // $response = $this->auth(true)->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property is used\nto define roles or permissions that you want to assign to your testing users within a test class.\nWhen you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.kt)("p",null,"By setting the desired roles and permissions in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property,\nyou can conveniently configure the testing user with the necessary access rights for your test scenarios.\nThis ensures that the testing user has the appropriate privileges when interacting with the application during testing."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DeleteUserTest extends ApiTestCase\n{\n protected string $endpoint = 'delete@v1/users/{id}';\n // highlight-start\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'admin',\n ];\n // highlight-end\n \n public function testDeleteUser(): void\n {\n // The testing user will have the \"delete-users\" permission and \"admin\" role.\n // highlight-start\n $user = $this->getTestingUser();\n // highlight-end\n \n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertNoContent(); \n }\n}\n")),(0,i.kt)("h3",{id:"methods"},"Methods"),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUser"},"getTestingUser"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUserWithoutAccess"},"getTestingUserWithoutAccess"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#endpoint"},"endpoint"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#auth"},"auth")," "),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"makecall"},"makeCall"),(0,i.kt)("p",null,"To make a request to your application, you may invoke the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.kt)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.kt)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#fluent-json-testing"},"helpful assertions"),"\nthat allow you to inspect your application's responses."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->makeCall();\n\n$this->makeCall([\n 'email' => $userDetails['email'],\n 'password' => $userDetails['password'],\n]);\n\n$this->makeCall($data, $headers);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"injectid"},"injectId"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// user with ID 100\n// endpoint = 'get@v1/users/{id}'\n\n$this->injectId($user->id)->makeCall();\n")),(0,i.kt)("p",null,"In this example, the original endpoint is ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.kt)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.kt)("p",null,"By default, ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint to replace with the provided id. Remember\nto provide the third parameter if your endpoint expects an id with a different name."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}/articles/{id}'\n// You can also chain multiple `injectId` calls!\n\n$this->injectId($articles->id)->injectId($user->id, replace: '{user_id}')->makeCall();\n")),(0,i.kt)("p",null,"When the ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method will automatically encode the provided ID before injecting it into the endpoint.\nHowever, you have the option to control this behavior by using the second parameter of the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.kt)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}'\n\n// this will encode the id automatically\n$this->injectId($user->id, skipEncoding: false, replace: '{user_id}')->makeCall($data);\n// this will skip the encoding\n$this->injectId($user->getHashedKey(), skipEncoding: true, replace: '{user_id}')->makeCall($data);\n")),(0,i.kt)("p",null,"By utilizing the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method, you can dynamically inject an ID into the endpoint,\nallowing you to test specific resources or scenarios that depend on resource identifiers."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.kt)("p",null,"When you call ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nit returns a testing user instance with randomly generated attributes and all the roles and permissions\nspecified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property.\nThis ensures that the testing user has the appropriate access rights for the defined roles and permissions.\nHowever,\nyou also have the flexibility\nto override these attributes and access rights by passing the desired values as arguments to the method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// The testing user will be created with randomly generated attributes \n// and will inherit the roles and permissions specified in the `$access` property.\n$user = $this->getTestingUser();\n\n// The testing user will be created with the provided attributes and access rights.\n$user = $this->getTestingUser([\n 'email' => 'hello@mail.test',\n 'name' => 'Hello',\n 'password' => 'secret',\n], [\n 'permissions' => 'jump',\n 'roles' => 'jumper',\n]);\n")),(0,i.kt)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method is configured to work with the default Apiato configuration.\nHowever, if you are using a custom user model,\nyou will need to update the ",(0,i.kt)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php"),".\nThis configuration file allows you\nto specify your custom user model and the corresponding model factory state for testing."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUserWithoutAccess")," method allows you to obtain a testing user instance that doesn't have any assigned permissions or roles.\nIt is a shortcut for ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method allows you to specify the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default endpoint that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The order in which you call ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," method allows you\nto specify the authentication status of the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default authentication status that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.kt)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.kt)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.kt)("p",null,"Here's an example of how to use ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.kt)("p",null,"In the code snippet above, ",(0,i.kt)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.kt)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.kt)("p",null,"In this case, the assertion will ignore the ",(0,i.kt)("inlineCode",{parentName:"p"},"id"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.kt)("p",null,"By using the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model does not contain any unexpected values,\nensuring that the model's attributes are not automatically casted."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.kt)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"expectedColumns")," array.\nThe array should be in the format ","['column_name' => 'column_type']",",\nwhere the column type is a string representing the expected data type of the column."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"getgatemock"},"getGateMock"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This assertion helps you to test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.kt)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method\nto determine whether a user has the necessary permissions to access a particular resource.\nThe primary objective is\nto test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// PUT '/users/{id}'\n\n// UpdateUserRequest.php\npublic function authorize(Gate $gate): bool\n{\n // Here, we check if the user's id sent in the request has the necessary permissions to 'update'.\n return $gate->allows('update', [User::find($this->id)]);\n}\n\n// UpdateUserRequestTest.php \npublic function testAuthorizeMethodGateCall(): void\n{\n $user = $this->getTestingUserWithoutAccess();\n $request = UpdateUserRequest::injectData([], $user)\n ->withUrlParameters(['id' => $user->id]);\n // If the id is sent as a body parameter in the request, you can use the following:\n // $request = UpdateUserRequest::injectData(['id' => $user->getHashedKey()], $ user);\n \n $gateMock = $this->getGateMock(policyMethodName: 'update', args: [\n // Ensure you obtain a fresh model instance; using the $user variable directly will cause the test to fail.\n User::find($user->id),\n ]);\n \n $this->assertTrue($request->authorize($gateMock));\n}\n")),(0,i.kt)("p",null,"In this code, we're examining the testing of the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method within a FormRequest class.\nThe main objective is to confirm that it appropriately interacts with Laravel's Gate functionality.\nThe test ensures that the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct parameters,\nchecking if users have the required permissions to perform updates.\nIf the authorization logic is correctly implemented, this test should pass,\nensuring that only users with the necessary permissions can perform updates."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"inids"},"inIds"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.kt)("p",null,"By leveraging the ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method, you can streamline your testing process when working with hashed identifiers,\nensuring that the expected hashed IDs are present within your model collections."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in the test file.\nInstead, it will be transformed into a helper function that you can utilize anywhere in your application.")),(0,i.kt)("h2",{id:"faker"},"Faker"),(0,i.kt)("p",null,"An instance of ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/FakerPHP/Faker"},"Faker")," is automatically provided in every test class, allowing you to generate fake data easily.\nYou can access it using ",(0,i.kt)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.kt)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.kt)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.kt)("p",null,"To test your application using live testing data,\nsuch as creating items in an inventory, you can utilize the feature designed specifically for this purpose.\nIt allows for the automatic generation of testing data,\nwhich can be helpful during staging or when real people are testing your application."),(0,i.kt)("p",null,"To create your live testing data, navigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Seeder/SeedTestingData.php")," seeder class.\nWithin this class, you can define the logic and data generation process for your testing data."),(0,i.kt)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.kt)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1db39f58.8d5f8d47.js b/assets/js/1db39f58.8d5f8d47.js deleted file mode 100644 index b2a2efeaa..000000000 --- a/assets/js/1db39f58.8d5f8d47.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[3022],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>m});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,s=e.originalType,l=e.parentName,d=r(e,["components","mdxType","originalType","parentName"]),h=p(n),c=i,m=h["".concat(l,".").concat(c)]||h[c]||u[c]||s;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,o=new Array(s);o[0]=c;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[h]="string"==typeof e?e:i,o[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const s={title:"Tests",tags:["component","optional-component","test"]},o=void 0,r={unversionedId:"components/optional-components/tests",id:"version-12.x/components/optional-components/tests",title:"Tests",description:"Apiato is built with testing in mind.",source:"@site/versioned_docs/version-12.x/components/optional-components/tests.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/tests",permalink:"/docs/components/optional-components/tests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/tests.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"test",permalink:"/docs/tags/test"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Tests",tags:["component","optional-component","test"]},sidebar:"tutorialSidebar",previous:{title:"Service Providers",permalink:"/docs/components/optional-components/service-providers"},next:{title:"Values",permalink:"/docs/components/optional-components/values"}},l={},p=[{value:"Definitions",id:"definitions",level:2},{value:"Unit tests",id:"unit-tests",level:4},{value:"Functional tests",id:"functional-tests",level:4},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Writing Tests",id:"writing-tests",level:2},{value:"Functional Test Helpers",id:"functional-test-helpers",level:2},{value:"Properties",id:"properties",level:3},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"access",id:"access",level:4},{value:"Methods",id:"methods",level:3},{value:"makeCall",id:"makecall",level:4},{value:"injectId",id:"injectid",level:4},{value:"getTestingUser",id:"gettestinguser",level:4},{value:"getTestingUserWithoutAccess",id:"gettestinguserwithoutaccess",level:4},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"Available Assertions",id:"available-assertions",level:2},{value:"assertModelCastsIsEmpty",id:"assertmodelcastsisempty",level:4},{value:"assertDatabaseTable",id:"assertdatabasetable",level:4},{value:"getGateMock",id:"getgatemock",level:4},{value:"inIds",id:"inids",level:4},{value:"Faker",id:"faker",level:2},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},h="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(h,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato is built with testing in mind.\nIn fact,\nsupport for testing with PHPUnit is included out of the box\nand a ",(0,i.kt)("inlineCode",{parentName:"p"},"phpunit.xml")," file is already set up for your application.\nIn addition to the testing capabilities provided by Laravel,\nApiato enhances the testing experience by including convenient helper methods.\nThese methods enable you to write expressive tests for your applications, further enhancing the testing process.\nYou can refer to Laravel documentation on ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.kt)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.kt)("h2",{id:"definitions"},"Definitions"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"unit-tests"},"Unit tests"),(0,i.kt)("p",null,"Unit tests are tests that focus on a very small, isolated portion of your code.\nIn fact, most unit tests probably focus on a single method."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"functional-tests"},"Functional tests"),(0,i.kt)("p",null,"Functional tests may test a larger portion of your code,\nincluding how several objects interact with each other or even a full HTTP request to a JSON endpoint.\nGenerally, most of your tests should be functional tests.\nThese types of tests provide the most confidence that your system as a whole is functioning as intended."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All container-specific Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All ",(0,i.kt)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All API Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All WEB Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All TestCases MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class. e.g.:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")),(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u251c\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTaskTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UnitTestCase.php\n\u2502 \u2514\u2500\u2500 UI\n\u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2514\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 ApiTestCase.php\n\u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 LoginTest.php\n\u2502 \u2502 \u251c\u2500\u2500 LogoutTest.php\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 WebTestCase.php\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Tests\n \u251c\u2500\u2500 Unit\n \u2502 \u251c\u2500\u2500 UrlRuleTest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 TestCase.php\n")),(0,i.kt)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.kt)("p",null,"Unit tests are defined in the same manner as you would define them in Laravel.\nHowever, Functional tests follow a distinct approach.\nHere's an example of how to write functional tests:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\UI\\API\\Tests\\Functional;\n\nuse App\\Containers\\AppSection\\User\\UI\\API\\Tests\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\n/**\n * @group user\n * @group api\n */\nclass FindUserByIdTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users/{id}';\n protected bool $auth = true;\n protected array $access = [\n 'permissions' => 'search-users',\n 'roles' => '',\n ];\n\n public function testFindUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertOk();\n $response->assertJson(\n static fn (AssertableJson $json) => $json->has('data')\n ->where('data.id', \\Hashids::encode($user->id))\n ->etc()\n );\n }\n}\n")),(0,i.kt)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.kt)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.kt)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.kt)("h3",{id:"properties"},"Properties"),(0,i.kt)("p",null,"Certain test helper methods access properties defined in your test class to execute their tasks effectively.\nBelow, we will explore these properties and their corresponding methods:"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.kt)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class FindUserByIdTest extends ApiTestCase\n{\n // highlight-start\n protected string $endpoint = 'get@v1/profile';\n // highlight-end\n \n public function testGetAuthenticatedUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->makeCall();\n // You can override the \"endpoint\" property in specific test methods\n // $response = $this->endpoint('get@v1/users')->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property is used to determine whether the endpoint being called requires authentication or not in your test class.\nIf you do not explicitly define the ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property in your test class, it will be defaulted to ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.kt)("p",null,"In the context of testing, when ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method will handle authentication by creating a testing user\n(if one is not already available) and injecting their access token into the headers before making the API call."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class ListUsersTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users';\n // highlight-start\n protected bool $auth = false;\n // highlight-end\n \n public function testListUsers(): void\n {\n $response = $this->makeCall();\n // You can override the \"auth\" property in specific test methods\n // $response = $this->auth(true)->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property is used\nto define roles or permissions that you want to assign to your testing users within a test class.\nWhen you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property."),(0,i.kt)("p",null,"By setting the desired roles and permissions in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property,\nyou can conveniently configure the testing user with the necessary access rights for your test scenarios.\nThis ensures that the testing user has the appropriate privileges when interacting with the application during testing."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DeleteUserTest extends ApiTestCase\n{\n protected string $endpoint = 'delete@v1/users/{id}';\n // highlight-start\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'admin',\n ];\n // highlight-end\n \n public function testDeleteUser(): void\n {\n // The testing user will have the \"delete-users\" permission and \"admin\" role.\n // highlight-start\n $user = $this->getTestingUser();\n // highlight-end\n \n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertNoContent(); \n }\n}\n")),(0,i.kt)("h3",{id:"methods"},"Methods"),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUser"},"getTestingUser"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUserWithoutAccess"},"getTestingUserWithoutAccess"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#endpoint"},"endpoint"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#auth"},"auth")," "),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"makecall"},"makeCall"),(0,i.kt)("p",null,"To make a request to your application, you may invoke the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.kt)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.kt)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#fluent-json-testing"},"helpful assertions"),"\nthat allow you to inspect your application's responses."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->makeCall();\n\n$this->makeCall([\n 'email' => $userDetails['email'],\n 'password' => $userDetails['password'],\n]);\n\n$this->makeCall($data, $headers);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"injectid"},"injectId"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// user with ID 100\n// endpoint = 'get@v1/users/{id}'\n\n$this->injectId($user->id)->makeCall();\n")),(0,i.kt)("p",null,"In this example, the original endpoint is ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.kt)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.kt)("p",null,"By default, ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint to replace with the provided id. Remember\nto provide the third parameter if your endpoint expects an id with a different name."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}/articles/{id}'\n// You can also chain multiple `injectId` calls!\n\n$this->injectId($articles->id)->injectId($user->id, replace: '{user_id}')->makeCall();\n")),(0,i.kt)("p",null,"When the ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method will automatically encode the provided ID before injecting it into the endpoint.\nHowever, you have the option to control this behavior by using the second parameter of the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.kt)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}'\n\n// this will encode the id automatically\n$this->injectId($user->id, skipEncoding: false, replace: '{user_id}')->makeCall($data);\n// this will skip the encoding\n$this->injectId($user->getHashedKey(), skipEncoding: true, replace: '{user_id}')->makeCall($data);\n")),(0,i.kt)("p",null,"By utilizing the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method, you can dynamically inject an ID into the endpoint,\nallowing you to test specific resources or scenarios that depend on resource identifiers."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.kt)("p",null,"When you call ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nit returns a testing user instance with randomly generated attributes and all the roles and permissions\nspecified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property.\nThis ensures that the testing user has the appropriate access rights for the defined roles and permissions.\nHowever,\nyou also have the flexibility\nto override these attributes and access rights by passing the desired values as arguments to the method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// The testing user will be created with randomly generated attributes \n// and will inherit the roles and permissions specified in the `access` property.\n$user = $this->getTestingUser();\n\n// The testing user will be created with the provided attributes and access rights.\n$user = $this->getTestingUser([\n 'email' => 'hello@mail.test',\n 'name' => 'Hello',\n 'password' => 'secret',\n], [\n 'permissions' => 'jump',\n 'roles' => 'jumper',\n]);\n")),(0,i.kt)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method is configured to work with the default Apiato configuration.\nHowever, if you are using a custom user model,\nyou will need to update the ",(0,i.kt)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php"),".\nThis configuration file allows you\nto specify your custom user model and the corresponding model factory state for testing."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUserWithoutAccess")," method allows you to obtain a testing user instance that doesn't have any assigned permissions or roles.\nIt is a shortcut for ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method allows you to specify the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default endpoint that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The order in which you call ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," method allows you\nto specify the authentication status of the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default authentication status that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.kt)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.kt)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model is empty.\nBy default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model includes the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.kt)("p",null,"Here's an example of how to use ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.kt)("p",null,"In the code snippet above, ",(0,i.kt)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of the model is empty,\nignoring the default ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.kt)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.kt)("p",null,"In this case, the assertion will ignore the ",(0,i.kt)("inlineCode",{parentName:"p"},"id"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of the model."),(0,i.kt)("p",null,"By using the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model does not contain any unexpected values,\nensuring that the model's attributes are not automatically casted."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.kt)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"expectedColumns")," array.\nThe array should be in the format ","['column_name' => 'column_type']",",\nwhere the column type is a string representing the expected data type of the column."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"getgatemock"},"getGateMock"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This assertion helps you to test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.kt)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method\nto determine whether a user has the necessary permissions to access a particular resource.\nThe primary objective is\nto test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// PUT '/users/{id}'\n\n// UpdateUserRequest.php\npublic function authorize(Gate $gate): bool\n{\n // Here, we check if the user's id sent in the request has the necessary permissions to 'update'.\n return $gate->allows('update', [User::find($this->id)]);\n}\n\n// UpdateUserRequestTest.php \npublic function testAuthorizeMethodGateCall(): void\n{\n $user = $this->getTestingUserWithoutAccess();\n $request = UpdateUserRequest::injectData([], $user)\n ->withUrlParameters(['id' => $user->id]);\n // If the id is sent as a body parameter in the request, you can use the following:\n // $request = UpdateUserRequest::injectData(['id' => $user->getHashedKey()], $ user);\n \n $gateMock = $this->getGateMock(policyMethodName: 'update', args: [\n // Ensure you obtain a fresh model instance; using the $user variable directly will cause the test to fail.\n User::find($user->id),\n ]);\n \n $this->assertTrue($request->authorize($gateMock));\n}\n")),(0,i.kt)("p",null,"In this code, we're examining the testing of the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method within a FormRequest class.\nThe main objective is to confirm that it appropriately interacts with Laravel's Gate functionality.\nThe test ensures that the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct parameters,\nchecking if users have the required permissions to perform updates.\nIf the authorization logic is correctly implemented, this test should pass,\nensuring that only users with the necessary permissions can perform updates."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"inids"},"inIds"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.kt)("p",null,"By leveraging the ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method, you can streamline your testing process when working with hashed identifiers,\nensuring that the expected hashed IDs are present within your model collections."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in the test file.\nInstead, it will be transformed into a helper function that you can utilize anywhere in your application.")),(0,i.kt)("h2",{id:"faker"},"Faker"),(0,i.kt)("p",null,"An instance of ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/FakerPHP/Faker"},"Faker")," is automatically provided in every test class, allowing you to generate fake data easily.\nYou can access it using ",(0,i.kt)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.kt)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.kt)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.kt)("p",null,"To test your application using live testing data,\nsuch as creating items in an inventory, you can utilize the feature designed specifically for this purpose.\nIt allows for the automatic generation of testing data,\nwhich can be helpful during staging or when real people are testing your application."),(0,i.kt)("p",null,"To create your live testing data, navigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Seeder/SeedTestingData.php")," seeder class.\nWithin this class, you can define the logic and data generation process for your testing data."),(0,i.kt)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.kt)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2e3626b2.91971aff.js b/assets/js/2e3626b2.91971aff.js deleted file mode 100644 index 4001ea657..000000000 --- a/assets/js/2e3626b2.91971aff.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2827],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||r;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:i,o[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const r={sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},o=void 0,s={unversionedId:"components/main-components/requests",id:"components/main-components/requests",title:"Requests",description:"Requests components are a way to interact with the current HTTP request",source:"@site/docs/components/main-components/requests.md",sourceDirName:"components/main-components",slug:"/components/main-components/requests",permalink:"/docs/next/components/main-components/requests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/main-components/requests.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"main-component",permalink:"/docs/next/tags/main-component"},{label:"request",permalink:"/docs/next/tags/request"},{label:"route",permalink:"/docs/next/tags/route"},{label:"controller",permalink:"/docs/next/tags/controller"},{label:"action",permalink:"/docs/next/tags/action"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:3,frontMatter:{sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},sidebar:"tutorialSidebar",previous:{title:"Controllers",permalink:"/docs/next/components/main-components/controllers"},next:{title:"Actions",permalink:"/docs/next/components/main-components/actions"}},l={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Validation",id:"validation",level:2},{value:"Request Properties",id:"request-properties",level:2},{value:"access",id:"access",level:3},{value:"decode",id:"decode",level:3},{value:"urlParameters",id:"urlparameters",level:3},{value:"Helper Methods",id:"helper-methods",level:2},{value:"check",id:"check",level:3},{value:"hasAccess",id:"hasaccess",level:3},{value:"sanitizeInput",id:"sanitizeinput",level:3},{value:"getInputByKey",id:"getinputbykey",level:3},{value:"mapInput",id:"mapinput",level:3},{value:"injectData",id:"injectdata",level:3},{value:"withUrlParameters",id:"withurlparameters",level:3},{value:"Custom Authorize Methods",id:"custom-authorize-methods",level:2},{value:"Bypass Authorization",id:"bypass-authorization",level:2},{value:"Force Accept Header",id:"force-accept-header",level:2},{value:"Etag",id:"etag",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/requests"},"Requests")," components are a way to interact with the current HTTP request\nbeing handled by your application as well as retrieve the input,\ncookies, and files that were submitted with the request."),(0,i.kt)("p",null,"To generate new requests you may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:request")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:request\n")),(0,i.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,i.kt)("p",null,"Read ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,i.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Requests)")),"."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Validation rules MUST be defined in the ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method."),(0,i.kt)("li",{parentName:"ul"},"All API Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Web Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Requests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Requests\\Request")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRequest"),"."))),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method, returning an array of validation rules."),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"authorize")," method, returning a boolean value.")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u251c\u2500\u2500 API\n \u2502 \u2514\u2500\u2500 Requests\n \u2502 \u251c\u2500\u2500 CreateUserRequest.php\n \u2502 \u251c\u2500\u2500 DeleteUserRequest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 WEB\n \u2514\u2500\u2500 Requests\n \u251c\u2500\u2500 Login.php\n \u251c\u2500\u2500 Logout.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n protected array $access = [];\n protected array $decode = [];\n protected array $urlParameters = [];\n\n public function rules(): array\n {\n return [\n 'field' => 'min:3|max:50',\n ];\n }\n\n public function authorize(): bool\n {\n return true;\n }\n}\n")),(0,i.kt)("h2",{id:"validation"},"Validation"),(0,i.kt)("p",null,"In Apiato,\nvalidation of incoming requests follows the\nLaravel ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/validation#form-request-validation"},"Form Request Validation")," approach."),(0,i.kt)("p",null,"Validation rules are defined within the respective Request class.\nThese rules are automatically enforced when a Request is injected into a Controller."),(0,i.kt)("p",null,"Here's an example of a Request class with validation rules:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass RegisterUserRequest extends ParentRequest\n{\n public function rules(): array\n {\n return [\n 'email' => 'required|email|max:200|unique:users,email',\n 'password' => 'required|min:20|max:300',\n 'name' => ['required', 'min:2', 'max:400'],\n ];\n }\n\n}\n")),(0,i.kt)("p",null,"And here's how you would use this Request class within a Controller:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"public function __invoke(RegisterUserRequest $request)\n{\n $user = app(RegisterUserAction::class)->run($request);\n \n return $this->transform($user, UserTransformer::class);\n}\n")),(0,i.kt)("p",null,"In this example,\nthe validation rules defined in ",(0,i.kt)("inlineCode",{parentName:"p"},"RegisterUserRequest")," will be automatically applied\nbefore the ",(0,i.kt)("inlineCode",{parentName:"p"},"__invoke")," method is executed.\nIf the validation fails, an appropriate error response will be generated."),(0,i.kt)("h2",{id:"request-properties"},"Request Properties"),(0,i.kt)("p",null,"Apiato introduces new properties to the Request Class that enhance its functionality."),(0,i.kt)("h3",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property allows you to define Roles and Permissions that can access a specific endpoint.\nIt's used by the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method to check if a user has the required Roles and Permissions to use that endpoint."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'manager'\n ];\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"You can also use the ",(0,i.kt)("inlineCode",{parentName:"p"},"array notation")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"pipe")," to define multiple Roles and Permissions."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected $access = [\n 'permissions' => ['delete-users', 'another-permissions'],\n 'roles' => 'manager|admin',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"If there's no need to set any roles or permissions,\nyou can simply set the ",(0,i.kt)("inlineCode",{parentName:"p"},"permissions")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"roles")," property to an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''"),", an empty array ",(0,i.kt)("inlineCode",{parentName:"p"},"[]"),", or ",(0,i.kt)("inlineCode",{parentName:"p"},"null"),".")),(0,i.kt)("h3",{id:"decode"},"decode"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"decode")," property is used to handle the decoding of Hashed IDs from the incoming Request."),(0,i.kt)("p",null,"When you enable the ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature, your application can receive Hashed IDs from users.\nThese Hashed IDs need to be decoded before they can be effectively validated.\nApiato facilitates this process\nby providing a property in its Requests class where you can specify which Hashed IDs need to be decoded.\nThis ensures that the validation procedure seamlessly integrates with Hashed IDs."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $decode = [\n 'user_id',\n 'item_id',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Keep in mind that validation rules relying on your ID, such as ",(0,i.kt)("inlineCode",{parentName:"p"},"exists:users,id"),",\nwill not function correctly unless you decode the ID before passing it to the validation process.")),(0,i.kt)("h3",{id:"urlparameters"},"urlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"urlParameters")," property simplifies the process of applying validation rules to URL parameters."),(0,i.kt)("p",null,"By default, Laravel doesn't provide validation for URL parameters (",(0,i.kt)("inlineCode",{parentName:"p"},"/stores/999/items"),").\nHowever, by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"urlParameters")," property, you can enable validation for these parameters.\nBy specifying the desired URL parameters within this property,\nyou not only enable validation but also gain direct access to these parameters from the Request object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// URL: /stores/{id}/items\n// GET /stores/999/items\nclass DemoRequest extends ParentRequest\n{\n protected array $urlParameters = [\n 'id',\n ];\n\n public function rules(): array\n {\n return [\n 'id' => 'integer', // url parameter\n ];\n }\n}\n")),(0,i.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,i.kt)("h3",{id:"check"},"check"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method is used to authorize the user to access the endpoint.\nIt accepts an array of methods names that will be called to check if the user has access or not.\nEach of those methods must return a boolean.\nTake a look at the following example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess|isOwner',\n 'isKing',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"Here we are passing the the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"isKing")," methods to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method.\nThen the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method follows the following rules and checks if the user has access or not:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The separator ",(0,i.kt)("inlineCode",{parentName:"li"},"|")," between the methods indicates an ",(0,i.kt)("inlineCode",{parentName:"li"},"OR")," operation."),(0,i.kt)("li",{parentName:"ul"},"The default operation between all methods in the array is ",(0,i.kt)("inlineCode",{parentName:"li"},"AND"),".")),(0,i.kt)("p",null,"So in the above example, the call to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method will be translated to:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"return ($this->hasAccess() || $this->isOwner()) && $this->isKing();\n")),(0,i.kt)("p",null,"And if the result of this operation is ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," then the user will be authorized to access the endpoint."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"hasAccess")," method is a ",(0,i.kt)("a",{parentName:"li",href:"#hasaccess"},"built-in authorization method"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"isKing")," methods are ",(0,i.kt)("a",{parentName:"li",href:"#custom-authorize-methods"},"custom authorization methods")))),(0,i.kt)("h3",{id:"hasaccess"},"hasAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method assesses a user's access rights based on the Request's ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property.\nIf the user has any of the specified Roles or Permissions, the method will return ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," otherwise it will\nreturn ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("h3",{id:"sanitizeinput"},"sanitizeInput"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method is employed to cleanse request data before its utilization within the application."),(0,i.kt)("p",null,"Particularly useful for ",(0,i.kt)("inlineCode",{parentName:"p"},"PATCH")," requests,\nwhere you may want\nto submit only the fields\nintended for modification to minimize traffic or perform partial updates to the corresponding database resource.\nTraditional checks for the presence or absence of specific keys in the request can lead to extensive ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks,\nsuch as:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"if ($request->has('data.name')) {\n $data['name'] = $request->input('data.name');\n}\n")),(0,i.kt)("p",null,"To circumvent these ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks, you might utilize ",(0,i.kt)("inlineCode",{parentName:"p"},"array_filter($data)")," to remove empty fields from the request.\nHowever, be aware that in PHP, both ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''")," are considered as ",(0,i.kt)("inlineCode",{parentName:"p"},"empty"),"."),(0,i.kt)("p",null,"For streamlining data sanitization when using ",(0,i.kt)("inlineCode",{parentName:"p"},"application/json")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"x-www-form-urlencoded"),",\nApiato provides the convenient ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "Demo",\n "description": "Some description",\n "is_private": false,\n "address": "",\n "foo": {\n "number": 1,\n "bar": "bar"\n }\n },\n "meta": "some meta data"\n}\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method enables you to specify a list of fields,\nemploying dot notation, to be accessed and extracted from the request."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$data = $request->sanitizeInput([\n 'data.description',\n 'data.is_private',\n 'data.address',\n 'data.foo.number',\n 'email', // will be ignored\n 'meta',\n]);\n")),(0,i.kt)("p",null,"The extracted data will appear as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},'[\n "data" => [\n "description" => "Some description"\n "is_private" => false,\n "address" => null, // empty string is converted to null by Laravel\n "foo" => [\n "number" => 1,\n ]\n ],\n "meta" => "some meta data",\n]\n')),(0,i.kt)("p",null,"Note that ",(0,i.kt)("inlineCode",{parentName:"p"},"email")," is excluded from the sanitized array, as it was absent in the request.\nAdditionally, any other fields not specified are omitted.\nIn essence, the method filters the request, retaining only the defined values."),(0,i.kt)("p",null,"You can also assign default values during the data sanitization process:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$sanitizedData = $request->sanitizeInput([\n 'name' => 'John', // If name is not provided, the default value will be set\n 'product.company.address' => 'Somewhere in the world', // dot notation is supported\n 'email',\n 'password'\n]);\n")),(0,i.kt)("h3",{id:"getinputbykey"},"getInputByKey"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getInputByKey")," method retrieves data from the ",(0,i.kt)("inlineCode",{parentName:"p"},"request")," by specifying the field name.\nSimilar to ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('key.here')"),", this method operates on the ",(0,i.kt)("inlineCode",{parentName:"p"},"decoded")," values instead of the original data."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,i.kt)("p",null,"While ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('id')")," would return ",(0,i.kt)("inlineCode",{parentName:"p"},'"XbPW7awNkzl83LD6"'),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"$request->getInputByKey('id')")," would return the decoded value\n(e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"4"),")."),(0,i.kt)("p",null,"Moreover, you can set a ",(0,i.kt)("inlineCode",{parentName:"p"},"default")," value to be returned if the key is absent or unset, like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->getInputByKey('data.name', 'Undefined')\n")),(0,i.kt)("h3",{id:"mapinput"},"mapInput"),(0,i.kt)("p",null,"In certain cases, you might need to remap input from the request to different fields.\nWhile manual field mapping is possible, you can also leverage the ",(0,i.kt)("inlineCode",{parentName:"p"},"mapInput"),' method for this purpose.\nThis helper method allows you to "redefine" keys within the request, making subsequent processing easier.'),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "John Doe"\n }\n}\n')),(0,i.kt)("p",null,"However, for processing purposes, you require the ",(0,i.kt)("inlineCode",{parentName:"p"},"username")," field instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"data.name"),"."),(0,i.kt)("p",null,"You can use the helper as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->mapInput([\n 'data.name' => 'username',\n]);\n")),(0,i.kt)("p",null,"The resulting structure would be:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "username": "John Doe"\n}\n')),(0,i.kt)("p",null,"And you can access the value as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->input('username');\n")),(0,i.kt)("h3",{id:"injectdata"},"injectData"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method allows you to inject data into the request.\nThis can be particularly helpful during testing\nwhen you wish to provide data directly to the request instead of sending it through the request body."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data);\n")),(0,i.kt)("h3",{id:"withurlparameters"},"withUrlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"withUrlParameters")," method enables you to inject URL parameters into the request.\nThis is especially useful when you need to include properties in the request that are not part of the request body\nbut are required for the request to be processed.\nThis method is often used in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data)\n ->withUrlParameters(['id' => 123]);\n")),(0,i.kt)("h2",{id:"custom-authorize-methods"},"Custom Authorize Methods"),(0,i.kt)("p",null,"The recommended approach for adding custom authorization functions is by using a Trait,\nwhich can be included in your Request classes."),(0,i.kt)("p",null,"For instance,\nlet's\ncreate an ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait with a single method\nnamed ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," to determine if the current user holds the role of an author."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"trait IsAuthorTrait\n{\n public function isAuthor(): bool\n {\n return $this->user()->hasRole('author');\n }\n}\n")),(0,i.kt)("p",null,"Subsequently, you can apply the ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait to a Request class,\nallowing the utilization of the ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," function within the authorization process."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'isAuthor',\n ]);\n }\n}\n")),(0,i.kt)("h2",{id:"bypass-authorization"},"Bypass Authorization"),(0,i.kt)("p",null,"To grant certain Roles access to all endpoints within the system without the need\nto define the role in each Request object,\nyou can follow this approach.\nThis is particularly beneficial when you want to provide unrestricted access to users with the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," role.\nTo implement this, define the relevant roles in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," as shown below:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'requests' => [\n 'allow-roles-to-access-all-routes' => ['admin'],\n],\n")),(0,i.kt)("h2",{id:"force-accept-header"},"Force Accept Header"),(0,i.kt)("p",null,"Typically, when making calls to a JSON API, you should include the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," HTTP header.\nIn Apiato, you have the option to either enforce users to send this header or allow them to skip it."),(0,i.kt)("p",null,"To enforce the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," header,\nnavigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," configuration file and set the ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"true"),"."),(0,i.kt)("p",null,"Conversely, if you wish to allow users to skip this header, set ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"Forcing the accept header is disabled by default.")),(0,i.kt)("h2",{id:"etag"},"Etag"),(0,i.kt)("p",null,"The ",(0,i.kt)("strong",{parentName:"p"},"ETag")," or ",(0,i.kt)("strong",{parentName:"p"},"entity tag")," is part of HTTP, the protocol for the World Wide Web.\nIt is one of several mechanisms that HTTP provides for Web cache validation,\nwhich allows a client to make conditional requests.\nThis mechanism allows caches to be more efficient and saves bandwidth,\nas a Web server does not need to send a full response if the content has not changed.\n(",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/HTTP_ETag"},"Wikipedia"),")"),(0,i.kt)("p",null,"Apiato offers support for Etag through the ",(0,i.kt)("inlineCode",{parentName:"p"},"Apiato\\Core\\Middlewares\\HttpProcessETagHeadersMiddleware")," middleware,\nwhich employs the Shallow technique.\nThis middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices."),(0,i.kt)("p",null,"Please note that this feature is ",(0,i.kt)("strong",{parentName:"p"},"disabled by default"),". To enable it, follow these steps:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Navigate to the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Configs/apiato.php")," configuration file."),(0,i.kt)("li",{parentName:"ol"},"Inside the configuration file, locate the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," configuration parameter."),(0,i.kt)("li",{parentName:"ol"},"Set the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," parameter to ",(0,i.kt)("inlineCode",{parentName:"li"},"true"),".")),(0,i.kt)("p",null,"Keep in mind that for this feature to function correctly, the client must include the ",(0,i.kt)("inlineCode",{parentName:"p"},"If-None-Match")," HTTP header,\nwhich corresponds to the ETag value, in their request."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/66d590de.36d1ccdc.js b/assets/js/2e3626b2.a445a068.js similarity index 50% rename from assets/js/66d590de.36d1ccdc.js rename to assets/js/2e3626b2.a445a068.js index dfcd311ea..ea8ebb0d3 100644 --- a/assets/js/66d590de.36d1ccdc.js +++ b/assets/js/2e3626b2.a445a068.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[9197],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||r;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:i,o[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const r={sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},o=void 0,s={unversionedId:"components/main-components/requests",id:"version-12.x/components/main-components/requests",title:"Requests",description:"Requests components are a way to interact with the current HTTP request",source:"@site/versioned_docs/version-12.x/components/main-components/requests.md",sourceDirName:"components/main-components",slug:"/components/main-components/requests",permalink:"/docs/components/main-components/requests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/main-components/requests.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"main-component",permalink:"/docs/tags/main-component"},{label:"request",permalink:"/docs/tags/request"},{label:"route",permalink:"/docs/tags/route"},{label:"controller",permalink:"/docs/tags/controller"},{label:"action",permalink:"/docs/tags/action"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:3,frontMatter:{sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},sidebar:"tutorialSidebar",previous:{title:"Controllers",permalink:"/docs/components/main-components/controllers"},next:{title:"Actions",permalink:"/docs/components/main-components/actions"}},l={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Validation",id:"validation",level:2},{value:"Request Properties",id:"request-properties",level:2},{value:"access",id:"access",level:3},{value:"decode",id:"decode",level:3},{value:"urlParameters",id:"urlparameters",level:3},{value:"Helper Methods",id:"helper-methods",level:2},{value:"check",id:"check",level:3},{value:"hasAccess",id:"hasaccess",level:3},{value:"sanitizeInput",id:"sanitizeinput",level:3},{value:"getInputByKey",id:"getinputbykey",level:3},{value:"mapInput",id:"mapinput",level:3},{value:"injectData",id:"injectdata",level:3},{value:"withUrlParameters",id:"withurlparameters",level:3},{value:"Custom Authorize Methods",id:"custom-authorize-methods",level:2},{value:"Bypass Authorization",id:"bypass-authorization",level:2},{value:"Force Accept Header",id:"force-accept-header",level:2},{value:"Etag",id:"etag",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/requests"},"Requests")," components are a way to interact with the current HTTP request\nbeing handled by your application as well as retrieve the input,\ncookies, and files that were submitted with the request."),(0,i.kt)("p",null,"To generate new requests you may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:request")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:request\n")),(0,i.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,i.kt)("p",null,"Read ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,i.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Requests)")),"."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Validation rules MUST be defined in the ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method."),(0,i.kt)("li",{parentName:"ul"},"All API Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Web Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Requests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Requests\\Request")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRequest"),"."))),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method, returning an array of validation rules."),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"authorize")," method, returning a boolean value.")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u251c\u2500\u2500 API\n \u2502 \u2514\u2500\u2500 Requests\n \u2502 \u251c\u2500\u2500 CreateUserRequest.php\n \u2502 \u251c\u2500\u2500 DeleteUserRequest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 WEB\n \u2514\u2500\u2500 Requests\n \u251c\u2500\u2500 Login.php\n \u251c\u2500\u2500 Logout.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n protected array $access = [];\n protected array $decode = [];\n protected array $urlParameters = [];\n\n public function rules(): array\n {\n return [\n 'field' => 'min:3|max:50',\n ];\n }\n\n public function authorize(): bool\n {\n return true;\n }\n}\n")),(0,i.kt)("h2",{id:"validation"},"Validation"),(0,i.kt)("p",null,"In Apiato,\nvalidation of incoming requests follows the\nLaravel ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/validation#form-request-validation"},"Form Request Validation")," approach."),(0,i.kt)("p",null,"Validation rules are defined within the respective Request class.\nThese rules are automatically enforced when a Request is injected into a Controller."),(0,i.kt)("p",null,"Here's an example of a Request class with validation rules:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass RegisterUserRequest extends ParentRequest\n{\n public function rules(): array\n {\n return [\n 'email' => 'required|email|max:200|unique:users,email',\n 'password' => 'required|min:20|max:300',\n 'name' => ['required', 'min:2', 'max:400'],\n ];\n }\n\n}\n")),(0,i.kt)("p",null,"And here's how you would use this Request class within a Controller:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"public function __invoke(RegisterUserRequest $request)\n{\n $user = app(RegisterUserAction::class)->run($request);\n \n return $this->transform($user, UserTransformer::class);\n}\n")),(0,i.kt)("p",null,"In this example,\nthe validation rules defined in ",(0,i.kt)("inlineCode",{parentName:"p"},"RegisterUserRequest")," will be automatically applied\nbefore the ",(0,i.kt)("inlineCode",{parentName:"p"},"__invoke")," method is executed.\nIf the validation fails, an appropriate error response will be generated."),(0,i.kt)("h2",{id:"request-properties"},"Request Properties"),(0,i.kt)("p",null,"Apiato introduces new properties to the Request Class that enhance its functionality."),(0,i.kt)("h3",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property allows you to define Roles and Permissions that can access a specific endpoint.\nIt's used by the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method to check if a user has the required Roles and Permissions to use that endpoint."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'manager'\n ];\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"You can also use the ",(0,i.kt)("inlineCode",{parentName:"p"},"array notation")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"pipe")," to define multiple Roles and Permissions."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected $access = [\n 'permissions' => ['delete-users', 'another-permissions'],\n 'roles' => 'manager|admin',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"If there's no need to set any roles or permissions,\nyou can simply set the ",(0,i.kt)("inlineCode",{parentName:"p"},"permissions")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"roles")," property to an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''"),", an empty array ",(0,i.kt)("inlineCode",{parentName:"p"},"[]"),", or ",(0,i.kt)("inlineCode",{parentName:"p"},"null"),".")),(0,i.kt)("h3",{id:"decode"},"decode"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"decode")," property is used to handle the decoding of Hashed IDs from the incoming Request."),(0,i.kt)("p",null,"When you enable the ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature, your application can receive Hashed IDs from users.\nThese Hashed IDs need to be decoded before they can be effectively validated.\nApiato facilitates this process\nby providing a property in its Requests class where you can specify which Hashed IDs need to be decoded.\nThis ensures that the validation procedure seamlessly integrates with Hashed IDs."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $decode = [\n 'user_id',\n 'item_id',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Keep in mind that validation rules relying on your ID, such as ",(0,i.kt)("inlineCode",{parentName:"p"},"exists:users,id"),",\nwill not function correctly unless you decode the ID before passing it to the validation process.")),(0,i.kt)("h3",{id:"urlparameters"},"urlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"urlParameters")," property simplifies the process of applying validation rules to URL parameters."),(0,i.kt)("p",null,"By default, Laravel doesn't provide validation for URL parameters (",(0,i.kt)("inlineCode",{parentName:"p"},"/stores/999/items"),").\nHowever, by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"urlParameters")," property, you can enable validation for these parameters.\nBy specifying the desired URL parameters within this property,\nyou not only enable validation but also gain direct access to these parameters from the Request object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// URL: /stores/{id}/items\n// GET /stores/999/items\nclass DemoRequest extends ParentRequest\n{\n protected array $urlParameters = [\n 'id',\n ];\n\n public function rules(): array\n {\n return [\n 'id' => 'integer', // url parameter\n ];\n }\n}\n")),(0,i.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,i.kt)("h3",{id:"check"},"check"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method is used to authorize the user to access the endpoint.\nIt accepts an array of methods names that will be called to check if the user has access or not.\nEach of those methods must return a boolean.\nTake a look at the following example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess|isOwner',\n 'isKing',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"Here we are passing the the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"isKing")," methods to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method.\nThen the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method follows the following rules and checks if the user has access or not:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The separator ",(0,i.kt)("inlineCode",{parentName:"li"},"|")," between the methods indicates an ",(0,i.kt)("inlineCode",{parentName:"li"},"OR")," operation."),(0,i.kt)("li",{parentName:"ul"},"The default operation between all methods in the array is ",(0,i.kt)("inlineCode",{parentName:"li"},"AND"),".")),(0,i.kt)("p",null,"So in the above example, the call to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method will be translated to:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"return ($this->hasAccess() || $this->isOwner()) && $this->isKing();\n")),(0,i.kt)("p",null,"And if the result of this operation is ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," then the user will be authorized to access the endpoint."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"hasAccess")," method is a ",(0,i.kt)("a",{parentName:"li",href:"#hasaccess"},"built-in authorization method"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"isKing")," methods are ",(0,i.kt)("a",{parentName:"li",href:"#custom-authorize-methods"},"custom authorization methods")))),(0,i.kt)("h3",{id:"hasaccess"},"hasAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method assesses a user's access rights based on the Request's ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property.\nIf the user has any of the specified Roles or Permissions, the method will return ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," otherwise it will\nreturn ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("h3",{id:"sanitizeinput"},"sanitizeInput"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method is employed to cleanse request data before its utilization within the application."),(0,i.kt)("p",null,"Particularly useful for ",(0,i.kt)("inlineCode",{parentName:"p"},"PATCH")," requests,\nwhere you may want\nto submit only the fields\nintended for modification to minimize traffic or perform partial updates to the corresponding database resource.\nTraditional checks for the presence or absence of specific keys in the request can lead to extensive ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks,\nsuch as:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"if ($request->has('data.name')) {\n $data['name'] = $request->input('data.name');\n}\n")),(0,i.kt)("p",null,"To circumvent these ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks, you might utilize ",(0,i.kt)("inlineCode",{parentName:"p"},"array_filter($data)")," to remove empty fields from the request.\nHowever, be aware that in PHP, both ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''")," are considered as ",(0,i.kt)("inlineCode",{parentName:"p"},"empty"),"."),(0,i.kt)("p",null,"For streamlining data sanitization when using ",(0,i.kt)("inlineCode",{parentName:"p"},"application/json")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"x-www-form-urlencoded"),",\nApiato provides the convenient ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "Demo",\n "description": "Some description",\n "is_private": false,\n "address": "",\n "foo": {\n "number": 1,\n "bar": "bar"\n }\n },\n "meta": "some meta data"\n}\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method enables you to specify a list of fields,\nemploying dot notation, to be accessed and extracted from the request."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$data = $request->sanitizeInput([\n 'data.description',\n 'data.is_private',\n 'data.address',\n 'data.foo.number',\n 'email', // will be ignored\n 'meta',\n]);\n")),(0,i.kt)("p",null,"The extracted data will appear as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},'[\n "data" => [\n "description" => "Some description"\n "is_private" => false,\n "address" => null, // empty string is converted to null by Laravel\n "foo" => [\n "number" => 1,\n ]\n ],\n "meta" => "some meta data",\n]\n')),(0,i.kt)("p",null,"Note that ",(0,i.kt)("inlineCode",{parentName:"p"},"email")," is excluded from the sanitized array, as it was absent in the request.\nAdditionally, any other fields not specified are omitted.\nIn essence, the method filters the request, retaining only the defined values."),(0,i.kt)("p",null,"You can also assign default values during the data sanitization process:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$sanitizedData = $request->sanitizeInput([\n 'name' => 'John', // If name is not provided, the default value will be set\n 'product.company.address' => 'Somewhere in the world', // dot notation is supported\n 'email',\n 'password'\n]);\n")),(0,i.kt)("h3",{id:"getinputbykey"},"getInputByKey"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getInputByKey")," method retrieves data from the ",(0,i.kt)("inlineCode",{parentName:"p"},"request")," by specifying the field name.\nSimilar to ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('key.here')"),", this method operates on the ",(0,i.kt)("inlineCode",{parentName:"p"},"decoded")," values instead of the original data."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,i.kt)("p",null,"While ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('id')")," would return ",(0,i.kt)("inlineCode",{parentName:"p"},'"XbPW7awNkzl83LD6"'),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"$request->getInputByKey('id')")," would return the decoded value\n(e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"4"),")."),(0,i.kt)("p",null,"Moreover, you can set a ",(0,i.kt)("inlineCode",{parentName:"p"},"default")," value to be returned if the key is absent or unset, like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->getInputByKey('data.name', 'Undefined')\n")),(0,i.kt)("h3",{id:"mapinput"},"mapInput"),(0,i.kt)("p",null,"In certain cases, you might need to remap input from the request to different fields.\nWhile manual field mapping is possible, you can also leverage the ",(0,i.kt)("inlineCode",{parentName:"p"},"mapInput"),' method for this purpose.\nThis helper method allows you to "redefine" keys within the request, making subsequent processing easier.'),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "John Doe"\n }\n}\n')),(0,i.kt)("p",null,"However, for processing purposes, you require the ",(0,i.kt)("inlineCode",{parentName:"p"},"username")," field instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"data.name"),"."),(0,i.kt)("p",null,"You can use the helper as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->mapInput([\n 'data.name' => 'username',\n]);\n")),(0,i.kt)("p",null,"The resulting structure would be:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "username": "John Doe"\n}\n')),(0,i.kt)("p",null,"And you can access the value as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->input('username');\n")),(0,i.kt)("h3",{id:"injectdata"},"injectData"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method allows you to inject data into the request.\nThis can be particularly helpful during testing\nwhen you wish to provide data directly to the request instead of sending it through the request body."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data);\n")),(0,i.kt)("h3",{id:"withurlparameters"},"withUrlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"withUrlParameters")," method enables you to inject URL parameters into the request.\nThis is especially useful when you need to include properties in the request that are not part of the request body\nbut are required for the request to be processed.\nThis method is often used in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data)\n ->withUrlParameters(['id' => 123]);\n")),(0,i.kt)("h2",{id:"custom-authorize-methods"},"Custom Authorize Methods"),(0,i.kt)("p",null,"The recommended approach for adding custom authorization functions is by using a Trait,\nwhich can be included in your Request classes."),(0,i.kt)("p",null,"For instance,\nlet's\ncreate an ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait with a single method\nnamed ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," to determine if the current user holds the role of an author."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"trait IsAuthorTrait\n{\n public function isAuthor(): bool\n {\n return $this->user()->hasRole('author');\n }\n}\n")),(0,i.kt)("p",null,"Subsequently, you can apply the ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait to a Request class,\nallowing the utilization of the ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," function within the authorization process."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'isAuthor',\n ]);\n }\n}\n")),(0,i.kt)("h2",{id:"bypass-authorization"},"Bypass Authorization"),(0,i.kt)("p",null,"To grant certain Roles access to all endpoints within the system without the need\nto define the role in each Request object,\nyou can follow this approach.\nThis is particularly beneficial when you want to provide unrestricted access to users with the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," role.\nTo implement this, define the relevant roles in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," as shown below:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'requests' => [\n 'allow-roles-to-access-all-routes' => ['admin'],\n],\n")),(0,i.kt)("h2",{id:"force-accept-header"},"Force Accept Header"),(0,i.kt)("p",null,"Typically, when making calls to a JSON API, you should include the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," HTTP header.\nIn Apiato, you have the option to either enforce users to send this header or allow them to skip it."),(0,i.kt)("p",null,"To enforce the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," header,\nnavigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," configuration file and set the ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"true"),"."),(0,i.kt)("p",null,"Conversely, if you wish to allow users to skip this header, set ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"Forcing the accept header is disabled by default.")),(0,i.kt)("h2",{id:"etag"},"Etag"),(0,i.kt)("p",null,"The ",(0,i.kt)("strong",{parentName:"p"},"ETag")," or ",(0,i.kt)("strong",{parentName:"p"},"entity tag")," is part of HTTP, the protocol for the World Wide Web.\nIt is one of several mechanisms that HTTP provides for Web cache validation,\nwhich allows a client to make conditional requests.\nThis mechanism allows caches to be more efficient and saves bandwidth,\nas a Web server does not need to send a full response if the content has not changed.\n(",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/HTTP_ETag"},"Wikipedia"),")"),(0,i.kt)("p",null,"Apiato offers support for Etag through the ",(0,i.kt)("inlineCode",{parentName:"p"},"Apiato\\Core\\Middlewares\\HttpProcessETagHeadersMiddleware")," middleware,\nwhich employs the Shallow technique.\nThis middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices."),(0,i.kt)("p",null,"Please note that this feature is ",(0,i.kt)("strong",{parentName:"p"},"disabled by default"),". To enable it, follow these steps:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Navigate to the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Configs/apiato.php")," configuration file."),(0,i.kt)("li",{parentName:"ol"},"Inside the configuration file, locate the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," configuration parameter."),(0,i.kt)("li",{parentName:"ol"},"Set the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," parameter to ",(0,i.kt)("inlineCode",{parentName:"li"},"true"),".")),(0,i.kt)("p",null,"Keep in mind that for this feature to function correctly, the client must include the ",(0,i.kt)("inlineCode",{parentName:"p"},"If-None-Match")," HTTP header,\nwhich corresponds to the ETag value, in their request."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2827],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||r;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:i,o[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const r={sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},o=void 0,s={unversionedId:"components/main-components/requests",id:"components/main-components/requests",title:"Requests",description:"Requests components are a way to interact with the current HTTP request",source:"@site/docs/components/main-components/requests.md",sourceDirName:"components/main-components",slug:"/components/main-components/requests",permalink:"/docs/next/components/main-components/requests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/main-components/requests.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"main-component",permalink:"/docs/next/tags/main-component"},{label:"request",permalink:"/docs/next/tags/request"},{label:"route",permalink:"/docs/next/tags/route"},{label:"controller",permalink:"/docs/next/tags/controller"},{label:"action",permalink:"/docs/next/tags/action"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:3,frontMatter:{sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},sidebar:"tutorialSidebar",previous:{title:"Controllers",permalink:"/docs/next/components/main-components/controllers"},next:{title:"Actions",permalink:"/docs/next/components/main-components/actions"}},l={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Validation",id:"validation",level:2},{value:"Request Properties",id:"request-properties",level:2},{value:"access",id:"access",level:3},{value:"decode",id:"decode",level:3},{value:"urlParameters",id:"urlparameters",level:3},{value:"Helper Methods",id:"helper-methods",level:2},{value:"check",id:"check",level:3},{value:"hasAccess",id:"hasaccess",level:3},{value:"sanitizeInput",id:"sanitizeinput",level:3},{value:"getInputByKey",id:"getinputbykey",level:3},{value:"mapInput",id:"mapinput",level:3},{value:"injectData",id:"injectdata",level:3},{value:"withUrlParameters",id:"withurlparameters",level:3},{value:"Custom Authorize Methods",id:"custom-authorize-methods",level:2},{value:"Bypass Authorization",id:"bypass-authorization",level:2},{value:"Force Accept Header",id:"force-accept-header",level:2},{value:"Etag",id:"etag",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/requests"},"Requests")," components are a way to interact with the current HTTP request\nbeing handled by your application as well as retrieve the input,\ncookies, and files that were submitted with the request."),(0,i.kt)("p",null,"To generate new requests you may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:request")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:request\n")),(0,i.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,i.kt)("p",null,"Read ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,i.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Requests)")),"."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Validation rules MUST be defined in the ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method."),(0,i.kt)("li",{parentName:"ul"},"All API Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Web Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Requests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Requests\\Request")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRequest"),"."))),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method, returning an array of validation rules."),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"authorize")," method, returning a boolean value.")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u251c\u2500\u2500 API\n \u2502 \u2514\u2500\u2500 Requests\n \u2502 \u251c\u2500\u2500 CreateUserRequest.php\n \u2502 \u251c\u2500\u2500 DeleteUserRequest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 WEB\n \u2514\u2500\u2500 Requests\n \u251c\u2500\u2500 Login.php\n \u251c\u2500\u2500 Logout.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n protected array $access = [];\n protected array $decode = [];\n protected array $urlParameters = [];\n\n public function rules(): array\n {\n return [\n 'field' => 'min:3|max:50',\n ];\n }\n\n public function authorize(): bool\n {\n return true;\n }\n}\n")),(0,i.kt)("h2",{id:"validation"},"Validation"),(0,i.kt)("p",null,"In Apiato,\nvalidation of incoming requests follows the\nLaravel ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/validation#form-request-validation"},"Form Request Validation")," approach."),(0,i.kt)("p",null,"Validation rules are defined within the respective Request class.\nThese rules are automatically enforced when a Request is injected into a Controller."),(0,i.kt)("p",null,"Here's an example of a Request class with validation rules:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass RegisterUserRequest extends ParentRequest\n{\n public function rules(): array\n {\n return [\n 'email' => 'required|email|max:200|unique:users,email',\n 'password' => 'required|min:20|max:300',\n 'name' => ['required', 'min:2', 'max:400'],\n ];\n }\n\n}\n")),(0,i.kt)("p",null,"And here's how you would use this Request class within a Controller:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"public function __invoke(RegisterUserRequest $request)\n{\n $user = app(RegisterUserAction::class)->run($request);\n \n return $this->transform($user, UserTransformer::class);\n}\n")),(0,i.kt)("p",null,"In this example,\nthe validation rules defined in ",(0,i.kt)("inlineCode",{parentName:"p"},"RegisterUserRequest")," will be automatically applied\nbefore the ",(0,i.kt)("inlineCode",{parentName:"p"},"__invoke")," method is executed.\nIf the validation fails, an appropriate error response will be generated."),(0,i.kt)("h2",{id:"request-properties"},"Request Properties"),(0,i.kt)("p",null,"Apiato introduces new properties to the Request Class that enhance its functionality."),(0,i.kt)("h3",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property allows you to define Roles and Permissions that can access a specific endpoint.\nIt's used by the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method to check if a user has the required Roles and Permissions to use that endpoint."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'manager'\n ];\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"You can also use the ",(0,i.kt)("inlineCode",{parentName:"p"},"array notation")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"pipe")," to define multiple Roles and Permissions."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected $access = [\n 'permissions' => ['delete-users', 'another-permissions'],\n 'roles' => 'manager|admin',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"If there's no need to set any roles or permissions,\nyou can simply set the ",(0,i.kt)("inlineCode",{parentName:"p"},"$permissions")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"$roles")," property to an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''"),", an empty array ",(0,i.kt)("inlineCode",{parentName:"p"},"[]"),", or ",(0,i.kt)("inlineCode",{parentName:"p"},"null"),".")),(0,i.kt)("h3",{id:"decode"},"decode"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$decode")," property is used to handle the decoding of Hashed IDs from the incoming Request."),(0,i.kt)("p",null,"When you enable the ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature, your application can receive Hashed IDs from users.\nThese Hashed IDs need to be decoded before they can be effectively validated.\nApiato facilitates this process\nby providing a property in its Requests class where you can specify which Hashed IDs need to be decoded.\nThis ensures that the validation procedure seamlessly integrates with Hashed IDs."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $decode = [\n 'user_id',\n 'item_id',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Keep in mind that validation rules relying on your ID, such as ",(0,i.kt)("inlineCode",{parentName:"p"},"exists:users,id"),",\nwill not function correctly unless you decode the ID before passing it to the validation process.")),(0,i.kt)("h3",{id:"urlparameters"},"urlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$urlParameters")," property simplifies the process of applying validation rules to URL parameters."),(0,i.kt)("p",null,"By default, Laravel doesn't provide validation for URL parameters (",(0,i.kt)("inlineCode",{parentName:"p"},"/stores/999/items"),").\nHowever, by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"$urlParameters")," property, you can enable validation for these parameters.\nBy specifying the desired URL parameters within this property,\nyou not only enable validation but also gain direct access to these parameters from the Request object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// URL: /stores/{id}/items\n// GET /stores/999/items\nclass DemoRequest extends ParentRequest\n{\n protected array $urlParameters = [\n 'id',\n ];\n\n public function rules(): array\n {\n return [\n 'id' => 'integer', // url parameter\n ];\n }\n}\n")),(0,i.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,i.kt)("h3",{id:"check"},"check"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method is used to authorize the user to access the endpoint.\nIt accepts an array of methods names that will be called to check if the user has access or not.\nEach of those methods must return a boolean.\nTake a look at the following example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess|isOwner',\n 'isKing',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"Here we are passing the the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"isKing")," methods to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method.\nThen the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method follows the following rules and checks if the user has access or not:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The separator ",(0,i.kt)("inlineCode",{parentName:"li"},"|")," between the methods indicates an ",(0,i.kt)("inlineCode",{parentName:"li"},"OR")," operation."),(0,i.kt)("li",{parentName:"ul"},"The default operation between all methods in the array is ",(0,i.kt)("inlineCode",{parentName:"li"},"AND"),".")),(0,i.kt)("p",null,"So in the above example, the call to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method will be translated to:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"return ($this->hasAccess() || $this->isOwner()) && $this->isKing();\n")),(0,i.kt)("p",null,"And if the result of this operation is ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," then the user will be authorized to access the endpoint."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"hasAccess")," method is a ",(0,i.kt)("a",{parentName:"li",href:"#hasaccess"},"built-in authorization method"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"isKing")," methods are ",(0,i.kt)("a",{parentName:"li",href:"#custom-authorize-methods"},"custom authorization methods")))),(0,i.kt)("h3",{id:"hasaccess"},"hasAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method assesses a user's access rights based on the Request's ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property.\nIf the user has any of the specified Roles or Permissions, the method will return ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," otherwise it will\nreturn ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("h3",{id:"sanitizeinput"},"sanitizeInput"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method is employed to cleanse request data before its utilization within the application."),(0,i.kt)("p",null,"Particularly useful for ",(0,i.kt)("inlineCode",{parentName:"p"},"PATCH")," requests,\nwhere you may want\nto submit only the fields\nintended for modification to minimize traffic or perform partial updates to the corresponding database resource.\nTraditional checks for the presence or absence of specific keys in the request can lead to extensive ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks,\nsuch as:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"if ($request->has('data.name')) {\n $data['name'] = $request->input('data.name');\n}\n")),(0,i.kt)("p",null,"To circumvent these ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks, you might utilize ",(0,i.kt)("inlineCode",{parentName:"p"},"array_filter($data)")," to remove empty fields from the request.\nHowever, be aware that in PHP, both ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''")," are considered as ",(0,i.kt)("inlineCode",{parentName:"p"},"empty"),"."),(0,i.kt)("p",null,"For streamlining data sanitization when using ",(0,i.kt)("inlineCode",{parentName:"p"},"application/json")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"x-www-form-urlencoded"),",\nApiato provides the convenient ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "Demo",\n "description": "Some description",\n "is_private": false,\n "address": "",\n "foo": {\n "number": 1,\n "bar": "bar"\n }\n },\n "meta": "some meta data"\n}\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method enables you to specify a list of fields,\nemploying dot notation, to be accessed and extracted from the request."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$data = $request->sanitizeInput([\n 'data.description',\n 'data.is_private',\n 'data.address',\n 'data.foo.number',\n 'email', // will be ignored\n 'meta',\n]);\n")),(0,i.kt)("p",null,"The extracted data will appear as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},'[\n "data" => [\n "description" => "Some description"\n "is_private" => false,\n "address" => null, // empty string is converted to null by Laravel\n "foo" => [\n "number" => 1,\n ]\n ],\n "meta" => "some meta data",\n]\n')),(0,i.kt)("p",null,"Note that ",(0,i.kt)("inlineCode",{parentName:"p"},"email")," is excluded from the sanitized array, as it was absent in the request.\nAdditionally, any other fields not specified are omitted.\nIn essence, the method filters the request, retaining only the defined values."),(0,i.kt)("p",null,"You can also assign default values during the data sanitization process:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$sanitizedData = $request->sanitizeInput([\n 'name' => 'John', // If name is not provided, the default value will be set\n 'product.company.address' => 'Somewhere in the world', // dot notation is supported\n 'email',\n 'password'\n]);\n")),(0,i.kt)("h3",{id:"getinputbykey"},"getInputByKey"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getInputByKey")," method retrieves data from the ",(0,i.kt)("inlineCode",{parentName:"p"},"request")," by specifying the field name.\nSimilar to ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('key.here')"),", this method operates on the ",(0,i.kt)("inlineCode",{parentName:"p"},"decoded")," values instead of the original data."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,i.kt)("p",null,"While ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('id')")," would return ",(0,i.kt)("inlineCode",{parentName:"p"},'"XbPW7awNkzl83LD6"'),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"$request->getInputByKey('id')")," would return the decoded value\n(e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"4"),")."),(0,i.kt)("p",null,"Moreover, you can set a ",(0,i.kt)("inlineCode",{parentName:"p"},"default")," value to be returned if the key is absent or unset, like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->getInputByKey('data.name', 'Undefined')\n")),(0,i.kt)("h3",{id:"mapinput"},"mapInput"),(0,i.kt)("p",null,"In certain cases, you might need to remap input from the request to different fields.\nWhile manual field mapping is possible, you can also leverage the ",(0,i.kt)("inlineCode",{parentName:"p"},"mapInput"),' method for this purpose.\nThis helper method allows you to "redefine" keys within the request, making subsequent processing easier.'),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "John Doe"\n }\n}\n')),(0,i.kt)("p",null,"However, for processing purposes, you require the ",(0,i.kt)("inlineCode",{parentName:"p"},"username")," field instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"data.name"),"."),(0,i.kt)("p",null,"You can use the helper as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->mapInput([\n 'data.name' => 'username',\n]);\n")),(0,i.kt)("p",null,"The resulting structure would be:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "username": "John Doe"\n}\n')),(0,i.kt)("p",null,"And you can access the value as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->input('username');\n")),(0,i.kt)("h3",{id:"injectdata"},"injectData"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method allows you to inject data into the request.\nThis can be particularly helpful during testing\nwhen you wish to provide data directly to the request instead of sending it through the request body."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data);\n")),(0,i.kt)("h3",{id:"withurlparameters"},"withUrlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"withUrlParameters")," method enables you to inject URL parameters into the request.\nThis is especially useful when you need to include properties in the request that are not part of the request body\nbut are required for the request to be processed.\nThis method is often used in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data)\n ->withUrlParameters(['id' => 123]);\n")),(0,i.kt)("h2",{id:"custom-authorize-methods"},"Custom Authorize Methods"),(0,i.kt)("p",null,"The recommended approach for adding custom authorization functions is by using a Trait,\nwhich can be included in your Request classes."),(0,i.kt)("p",null,"For instance,\nlet's\ncreate an ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait with a single method\nnamed ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," to determine if the current user holds the role of an author."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"trait IsAuthorTrait\n{\n public function isAuthor(): bool\n {\n return $this->user()->hasRole('author');\n }\n}\n")),(0,i.kt)("p",null,"Subsequently, you can apply the ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait to a Request class,\nallowing the utilization of the ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," function within the authorization process."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'isAuthor',\n ]);\n }\n}\n")),(0,i.kt)("h2",{id:"bypass-authorization"},"Bypass Authorization"),(0,i.kt)("p",null,"To grant certain Roles access to all endpoints within the system without the need\nto define the role in each Request object,\nyou can follow this approach.\nThis is particularly beneficial when you want to provide unrestricted access to users with the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," role.\nTo implement this, define the relevant roles in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," as shown below:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'requests' => [\n 'allow-roles-to-access-all-routes' => ['admin'],\n],\n")),(0,i.kt)("h2",{id:"force-accept-header"},"Force Accept Header"),(0,i.kt)("p",null,"Typically, when making calls to a JSON API, you should include the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," HTTP header.\nIn Apiato, you have the option to either enforce users to send this header or allow them to skip it."),(0,i.kt)("p",null,"To enforce the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," header,\nnavigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," configuration file and set the ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"true"),"."),(0,i.kt)("p",null,"Conversely, if you wish to allow users to skip this header, set ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"Forcing the accept header is disabled by default.")),(0,i.kt)("h2",{id:"etag"},"Etag"),(0,i.kt)("p",null,"The ",(0,i.kt)("strong",{parentName:"p"},"ETag")," or ",(0,i.kt)("strong",{parentName:"p"},"entity tag")," is part of HTTP, the protocol for the World Wide Web.\nIt is one of several mechanisms that HTTP provides for Web cache validation,\nwhich allows a client to make conditional requests.\nThis mechanism allows caches to be more efficient and saves bandwidth,\nas a Web server does not need to send a full response if the content has not changed.\n(",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/HTTP_ETag"},"Wikipedia"),")"),(0,i.kt)("p",null,"Apiato offers support for Etag through the ",(0,i.kt)("inlineCode",{parentName:"p"},"Apiato\\Core\\Middlewares\\HttpProcessETagHeadersMiddleware")," middleware,\nwhich employs the Shallow technique.\nThis middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices."),(0,i.kt)("p",null,"Please note that this feature is ",(0,i.kt)("strong",{parentName:"p"},"disabled by default"),". To enable it, follow these steps:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Navigate to the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Configs/apiato.php")," configuration file."),(0,i.kt)("li",{parentName:"ol"},"Inside the configuration file, locate the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," configuration parameter."),(0,i.kt)("li",{parentName:"ol"},"Set the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," parameter to ",(0,i.kt)("inlineCode",{parentName:"li"},"true"),".")),(0,i.kt)("p",null,"Keep in mind that for this feature to function correctly, the client must include the ",(0,i.kt)("inlineCode",{parentName:"p"},"If-None-Match")," HTTP header,\nwhich corresponds to the ETag value, in their request."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/66d590de.4bb031ee.js b/assets/js/66d590de.4bb031ee.js new file mode 100644 index 000000000..2699e3c8b --- /dev/null +++ b/assets/js/66d590de.4bb031ee.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[9197],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=i,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||r;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:i,o[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const r={sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},o=void 0,s={unversionedId:"components/main-components/requests",id:"version-12.x/components/main-components/requests",title:"Requests",description:"Requests components are a way to interact with the current HTTP request",source:"@site/versioned_docs/version-12.x/components/main-components/requests.md",sourceDirName:"components/main-components",slug:"/components/main-components/requests",permalink:"/docs/components/main-components/requests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/main-components/requests.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"main-component",permalink:"/docs/tags/main-component"},{label:"request",permalink:"/docs/tags/request"},{label:"route",permalink:"/docs/tags/route"},{label:"controller",permalink:"/docs/tags/controller"},{label:"action",permalink:"/docs/tags/action"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:3,frontMatter:{sidebar_position:3,title:"Requests",tags:["component","main-component","request","route","controller","action"]},sidebar:"tutorialSidebar",previous:{title:"Controllers",permalink:"/docs/components/main-components/controllers"},next:{title:"Actions",permalink:"/docs/components/main-components/actions"}},l={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Validation",id:"validation",level:2},{value:"Request Properties",id:"request-properties",level:2},{value:"access",id:"access",level:3},{value:"decode",id:"decode",level:3},{value:"urlParameters",id:"urlparameters",level:3},{value:"Helper Methods",id:"helper-methods",level:2},{value:"check",id:"check",level:3},{value:"hasAccess",id:"hasaccess",level:3},{value:"sanitizeInput",id:"sanitizeinput",level:3},{value:"getInputByKey",id:"getinputbykey",level:3},{value:"mapInput",id:"mapinput",level:3},{value:"injectData",id:"injectdata",level:3},{value:"withUrlParameters",id:"withurlparameters",level:3},{value:"Custom Authorize Methods",id:"custom-authorize-methods",level:2},{value:"Bypass Authorization",id:"bypass-authorization",level:2},{value:"Force Accept Header",id:"force-accept-header",level:2},{value:"Etag",id:"etag",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(d,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/requests"},"Requests")," components are a way to interact with the current HTTP request\nbeing handled by your application as well as retrieve the input,\ncookies, and files that were submitted with the request."),(0,i.kt)("p",null,"To generate new requests you may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:request")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:request\n")),(0,i.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,i.kt)("p",null,"Read ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,i.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Requests)")),"."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Validation rules MUST be defined in the ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method."),(0,i.kt)("li",{parentName:"ul"},"All API Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Web Requests MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Requests")," directory."),(0,i.kt)("li",{parentName:"ul"},"All Requests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Requests\\Request")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRequest"),"."))),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"rules")," method, returning an array of validation rules."),(0,i.kt)("li",{parentName:"ul"},"MUST have a public ",(0,i.kt)("inlineCode",{parentName:"li"},"authorize")," method, returning a boolean value.")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u251c\u2500\u2500 API\n \u2502 \u2514\u2500\u2500 Requests\n \u2502 \u251c\u2500\u2500 CreateUserRequest.php\n \u2502 \u251c\u2500\u2500 DeleteUserRequest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 WEB\n \u2514\u2500\u2500 Requests\n \u251c\u2500\u2500 Login.php\n \u251c\u2500\u2500 Logout.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n protected array $access = [];\n protected array $decode = [];\n protected array $urlParameters = [];\n\n public function rules(): array\n {\n return [\n 'field' => 'min:3|max:50',\n ];\n }\n\n public function authorize(): bool\n {\n return true;\n }\n}\n")),(0,i.kt)("h2",{id:"validation"},"Validation"),(0,i.kt)("p",null,"In Apiato,\nvalidation of incoming requests follows the\nLaravel ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/validation#form-request-validation"},"Form Request Validation")," approach."),(0,i.kt)("p",null,"Validation rules are defined within the respective Request class.\nThese rules are automatically enforced when a Request is injected into a Controller."),(0,i.kt)("p",null,"Here's an example of a Request class with validation rules:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass RegisterUserRequest extends ParentRequest\n{\n public function rules(): array\n {\n return [\n 'email' => 'required|email|max:200|unique:users,email',\n 'password' => 'required|min:20|max:300',\n 'name' => ['required', 'min:2', 'max:400'],\n ];\n }\n\n}\n")),(0,i.kt)("p",null,"And here's how you would use this Request class within a Controller:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"public function __invoke(RegisterUserRequest $request)\n{\n $user = app(RegisterUserAction::class)->run($request);\n \n return $this->transform($user, UserTransformer::class);\n}\n")),(0,i.kt)("p",null,"In this example,\nthe validation rules defined in ",(0,i.kt)("inlineCode",{parentName:"p"},"RegisterUserRequest")," will be automatically applied\nbefore the ",(0,i.kt)("inlineCode",{parentName:"p"},"__invoke")," method is executed.\nIf the validation fails, an appropriate error response will be generated."),(0,i.kt)("h2",{id:"request-properties"},"Request Properties"),(0,i.kt)("p",null,"Apiato introduces new properties to the Request Class that enhance its functionality."),(0,i.kt)("h3",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property allows you to define Roles and Permissions that can access a specific endpoint.\nIt's used by the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method to check if a user has the required Roles and Permissions to use that endpoint."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'manager'\n ];\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"You can also use the ",(0,i.kt)("inlineCode",{parentName:"p"},"array notation")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"pipe")," to define multiple Roles and Permissions."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected $access = [\n 'permissions' => ['delete-users', 'another-permissions'],\n 'roles' => 'manager|admin',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"If there's no need to set any roles or permissions,\nyou can simply set the ",(0,i.kt)("inlineCode",{parentName:"p"},"$permissions")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"$roles")," property to an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''"),", an empty array ",(0,i.kt)("inlineCode",{parentName:"p"},"[]"),", or ",(0,i.kt)("inlineCode",{parentName:"p"},"null"),".")),(0,i.kt)("h3",{id:"decode"},"decode"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$decode")," property is used to handle the decoding of Hashed IDs from the incoming Request."),(0,i.kt)("p",null,"When you enable the ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature, your application can receive Hashed IDs from users.\nThese Hashed IDs need to be decoded before they can be effectively validated.\nApiato facilitates this process\nby providing a property in its Requests class where you can specify which Hashed IDs need to be decoded.\nThis ensures that the validation procedure seamlessly integrates with Hashed IDs."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DemoRequest extends ParentRequest\n{\n protected array $decode = [\n 'user_id',\n 'item_id',\n ];\n \n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Keep in mind that validation rules relying on your ID, such as ",(0,i.kt)("inlineCode",{parentName:"p"},"exists:users,id"),",\nwill not function correctly unless you decode the ID before passing it to the validation process.")),(0,i.kt)("h3",{id:"urlparameters"},"urlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$urlParameters")," property simplifies the process of applying validation rules to URL parameters."),(0,i.kt)("p",null,"By default, Laravel doesn't provide validation for URL parameters (",(0,i.kt)("inlineCode",{parentName:"p"},"/stores/999/items"),").\nHowever, by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"$urlParameters")," property, you can enable validation for these parameters.\nBy specifying the desired URL parameters within this property,\nyou not only enable validation but also gain direct access to these parameters from the Request object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// URL: /stores/{id}/items\n// GET /stores/999/items\nclass DemoRequest extends ParentRequest\n{\n protected array $urlParameters = [\n 'id',\n ];\n\n public function rules(): array\n {\n return [\n 'id' => 'integer', // url parameter\n ];\n }\n}\n")),(0,i.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,i.kt)("h3",{id:"check"},"check"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method is used to authorize the user to access the endpoint.\nIt accepts an array of methods names that will be called to check if the user has access or not.\nEach of those methods must return a boolean.\nTake a look at the following example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'hasAccess|isOwner',\n 'isKing',\n ]);\n }\n}\n")),(0,i.kt)("p",null,"Here we are passing the the ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"isKing")," methods to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method.\nThen the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method follows the following rules and checks if the user has access or not:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The separator ",(0,i.kt)("inlineCode",{parentName:"li"},"|")," between the methods indicates an ",(0,i.kt)("inlineCode",{parentName:"li"},"OR")," operation."),(0,i.kt)("li",{parentName:"ul"},"The default operation between all methods in the array is ",(0,i.kt)("inlineCode",{parentName:"li"},"AND"),".")),(0,i.kt)("p",null,"So in the above example, the call to the ",(0,i.kt)("inlineCode",{parentName:"p"},"check")," method will be translated to:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"return ($this->hasAccess() || $this->isOwner()) && $this->isKing();\n")),(0,i.kt)("p",null,"And if the result of this operation is ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," then the user will be authorized to access the endpoint."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("ul",{parentName:"admonition"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"hasAccess")," method is a ",(0,i.kt)("a",{parentName:"li",href:"#hasaccess"},"built-in authorization method"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"isOwner")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"isKing")," methods are ",(0,i.kt)("a",{parentName:"li",href:"#custom-authorize-methods"},"custom authorization methods")))),(0,i.kt)("h3",{id:"hasaccess"},"hasAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"hasAccess")," method assesses a user's access rights based on the Request's ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property.\nIf the user has any of the specified Roles or Permissions, the method will return ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," otherwise it will\nreturn ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("h3",{id:"sanitizeinput"},"sanitizeInput"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method is employed to cleanse request data before its utilization within the application."),(0,i.kt)("p",null,"Particularly useful for ",(0,i.kt)("inlineCode",{parentName:"p"},"PATCH")," requests,\nwhere you may want\nto submit only the fields\nintended for modification to minimize traffic or perform partial updates to the corresponding database resource.\nTraditional checks for the presence or absence of specific keys in the request can lead to extensive ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks,\nsuch as:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"if ($request->has('data.name')) {\n $data['name'] = $request->input('data.name');\n}\n")),(0,i.kt)("p",null,"To circumvent these ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," blocks, you might utilize ",(0,i.kt)("inlineCode",{parentName:"p"},"array_filter($data)")," to remove empty fields from the request.\nHowever, be aware that in PHP, both ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and an empty string ",(0,i.kt)("inlineCode",{parentName:"p"},"''")," are considered as ",(0,i.kt)("inlineCode",{parentName:"p"},"empty"),"."),(0,i.kt)("p",null,"For streamlining data sanitization when using ",(0,i.kt)("inlineCode",{parentName:"p"},"application/json")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"x-www-form-urlencoded"),",\nApiato provides the convenient ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "Demo",\n "description": "Some description",\n "is_private": false,\n "address": "",\n "foo": {\n "number": 1,\n "bar": "bar"\n }\n },\n "meta": "some meta data"\n}\n')),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sanitizeInput")," method enables you to specify a list of fields,\nemploying dot notation, to be accessed and extracted from the request."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$data = $request->sanitizeInput([\n 'data.description',\n 'data.is_private',\n 'data.address',\n 'data.foo.number',\n 'email', // will be ignored\n 'meta',\n]);\n")),(0,i.kt)("p",null,"The extracted data will appear as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},'[\n "data" => [\n "description" => "Some description"\n "is_private" => false,\n "address" => null, // empty string is converted to null by Laravel\n "foo" => [\n "number" => 1,\n ]\n ],\n "meta" => "some meta data",\n]\n')),(0,i.kt)("p",null,"Note that ",(0,i.kt)("inlineCode",{parentName:"p"},"email")," is excluded from the sanitized array, as it was absent in the request.\nAdditionally, any other fields not specified are omitted.\nIn essence, the method filters the request, retaining only the defined values."),(0,i.kt)("p",null,"You can also assign default values during the data sanitization process:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$sanitizedData = $request->sanitizeInput([\n 'name' => 'John', // If name is not provided, the default value will be set\n 'product.company.address' => 'Somewhere in the world', // dot notation is supported\n 'email',\n 'password'\n]);\n")),(0,i.kt)("h3",{id:"getinputbykey"},"getInputByKey"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getInputByKey")," method retrieves data from the ",(0,i.kt)("inlineCode",{parentName:"p"},"request")," by specifying the field name.\nSimilar to ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('key.here')"),", this method operates on the ",(0,i.kt)("inlineCode",{parentName:"p"},"decoded")," values instead of the original data."),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,i.kt)("p",null,"While ",(0,i.kt)("inlineCode",{parentName:"p"},"$request->input('id')")," would return ",(0,i.kt)("inlineCode",{parentName:"p"},'"XbPW7awNkzl83LD6"'),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"$request->getInputByKey('id')")," would return the decoded value\n(e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"4"),")."),(0,i.kt)("p",null,"Moreover, you can set a ",(0,i.kt)("inlineCode",{parentName:"p"},"default")," value to be returned if the key is absent or unset, like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->getInputByKey('data.name', 'Undefined')\n")),(0,i.kt)("h3",{id:"mapinput"},"mapInput"),(0,i.kt)("p",null,"In certain cases, you might need to remap input from the request to different fields.\nWhile manual field mapping is possible, you can also leverage the ",(0,i.kt)("inlineCode",{parentName:"p"},"mapInput"),' method for this purpose.\nThis helper method allows you to "redefine" keys within the request, making subsequent processing easier.'),(0,i.kt)("p",null,"Consider the following request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "name": "John Doe"\n }\n}\n')),(0,i.kt)("p",null,"However, for processing purposes, you require the ",(0,i.kt)("inlineCode",{parentName:"p"},"username")," field instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"data.name"),"."),(0,i.kt)("p",null,"You can use the helper as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->mapInput([\n 'data.name' => 'username',\n]);\n")),(0,i.kt)("p",null,"The resulting structure would be:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "username": "John Doe"\n}\n')),(0,i.kt)("p",null,"And you can access the value as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request->input('username');\n")),(0,i.kt)("h3",{id:"injectdata"},"injectData"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method allows you to inject data into the request.\nThis can be particularly helpful during testing\nwhen you wish to provide data directly to the request instead of sending it through the request body."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data);\n")),(0,i.kt)("h3",{id:"withurlparameters"},"withUrlParameters"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"withUrlParameters")," method enables you to inject URL parameters into the request.\nThis is especially useful when you need to include properties in the request that are not part of the request body\nbut are required for the request to be processed.\nThis method is often used in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectData")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$request = RegisterUserRequest::injectData($data)\n ->withUrlParameters(['id' => 123]);\n")),(0,i.kt)("h2",{id:"custom-authorize-methods"},"Custom Authorize Methods"),(0,i.kt)("p",null,"The recommended approach for adding custom authorization functions is by using a Trait,\nwhich can be included in your Request classes."),(0,i.kt)("p",null,"For instance,\nlet's\ncreate an ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait with a single method\nnamed ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," to determine if the current user holds the role of an author."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"trait IsAuthorTrait\n{\n public function isAuthor(): bool\n {\n return $this->user()->hasRole('author');\n }\n}\n")),(0,i.kt)("p",null,"Subsequently, you can apply the ",(0,i.kt)("inlineCode",{parentName:"p"},"IsAuthorTrait")," Trait to a Request class,\nallowing the utilization of the ",(0,i.kt)("inlineCode",{parentName:"p"},"isAuthor")," function within the authorization process."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Requests\\Request as ParentRequest;\n\nclass DemoRequest extends ParentRequest\n{\n use IsAuthorTrait;\n\n // ...\n\n public function authorize(): bool\n {\n return $this->check([\n 'isAuthor',\n ]);\n }\n}\n")),(0,i.kt)("h2",{id:"bypass-authorization"},"Bypass Authorization"),(0,i.kt)("p",null,"To grant certain Roles access to all endpoints within the system without the need\nto define the role in each Request object,\nyou can follow this approach.\nThis is particularly beneficial when you want to provide unrestricted access to users with the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," role.\nTo implement this, define the relevant roles in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," as shown below:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'requests' => [\n 'allow-roles-to-access-all-routes' => ['admin'],\n],\n")),(0,i.kt)("h2",{id:"force-accept-header"},"Force Accept Header"),(0,i.kt)("p",null,"Typically, when making calls to a JSON API, you should include the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," HTTP header.\nIn Apiato, you have the option to either enforce users to send this header or allow them to skip it."),(0,i.kt)("p",null,"To enforce the ",(0,i.kt)("inlineCode",{parentName:"p"},"accept: application/json")," header,\nnavigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php")," configuration file and set the ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"true"),"."),(0,i.kt)("p",null,"Conversely, if you wish to allow users to skip this header, set ",(0,i.kt)("inlineCode",{parentName:"p"},"force-accept-header")," to ",(0,i.kt)("inlineCode",{parentName:"p"},"false"),"."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"Forcing the accept header is disabled by default.")),(0,i.kt)("h2",{id:"etag"},"Etag"),(0,i.kt)("p",null,"The ",(0,i.kt)("strong",{parentName:"p"},"ETag")," or ",(0,i.kt)("strong",{parentName:"p"},"entity tag")," is part of HTTP, the protocol for the World Wide Web.\nIt is one of several mechanisms that HTTP provides for Web cache validation,\nwhich allows a client to make conditional requests.\nThis mechanism allows caches to be more efficient and saves bandwidth,\nas a Web server does not need to send a full response if the content has not changed.\n(",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/HTTP_ETag"},"Wikipedia"),")"),(0,i.kt)("p",null,"Apiato offers support for Etag through the ",(0,i.kt)("inlineCode",{parentName:"p"},"Apiato\\Core\\Middlewares\\HttpProcessETagHeadersMiddleware")," middleware,\nwhich employs the Shallow technique.\nThis middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices."),(0,i.kt)("p",null,"Please note that this feature is ",(0,i.kt)("strong",{parentName:"p"},"disabled by default"),". To enable it, follow these steps:"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},"Navigate to the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Configs/apiato.php")," configuration file."),(0,i.kt)("li",{parentName:"ol"},"Inside the configuration file, locate the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," configuration parameter."),(0,i.kt)("li",{parentName:"ol"},"Set the ",(0,i.kt)("inlineCode",{parentName:"li"},"use-etag")," parameter to ",(0,i.kt)("inlineCode",{parentName:"li"},"true"),".")),(0,i.kt)("p",null,"Keep in mind that for this feature to function correctly, the client must include the ",(0,i.kt)("inlineCode",{parentName:"p"},"If-None-Match")," HTTP header,\nwhich corresponds to the ETag value, in their request."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8c9fccb5.7d80d97a.js b/assets/js/8c9fccb5.15219786.js similarity index 57% rename from assets/js/8c9fccb5.7d80d97a.js rename to assets/js/8c9fccb5.15219786.js index 0bb23ed4f..88f378db9 100644 --- a/assets/js/8c9fccb5.7d80d97a.js +++ b/assets/js/8c9fccb5.15219786.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8995],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),s=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},d=function(e){var t=s(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=s(n),m=i,h=u["".concat(l,".").concat(m)]||u[m]||c[m]||o;return n?a.createElement(h,r(r({ref:t},d),{},{components:n})):a.createElement(h,r({ref:t},d))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,r=new Array(o);r[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p[u]="string"==typeof e?e:i,r[1]=p;for(var s=2;s{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var a=n(87462),i=(n(67294),n(3905));const o={sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},r=void 0,p={unversionedId:"components/optional-components/repository/repositories",id:"components/optional-components/repository/repositories",title:"Repositories",description:"Apiato provides a powerful repository pattern implementation based on the L5 Repository package.",source:"@site/docs/components/optional-components/repository/repositories.md",sourceDirName:"components/optional-components/repository",slug:"/components/optional-components/repository/repositories",permalink:"/docs/next/components/optional-components/repository/repositories",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/repository/repositories.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"repository",permalink:"/docs/next/tags/repository"},{label:"criteria",permalink:"/docs/next/tags/criteria"},{label:"action",permalink:"/docs/next/tags/action"},{label:"task",permalink:"/docs/next/tags/task"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697552040,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},sidebar:"tutorialSidebar",previous:{title:"Policies",permalink:"/docs/next/components/optional-components/policies"},next:{title:"Criterias",permalink:"/docs/next/components/optional-components/repository/criterias"}},l={},s=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Linking Repositories & Models",id:"linking-repositories--models",level:2},{value:"Repositories & Models Auto-Linking",id:"repositories--models-auto-linking",level:3},{value:"Pagination",id:"pagination",level:2},{value:"Limiting Results Per Page",id:"limiting-results-per-page",level:3},{value:"Maximum Pagination Limit",id:"maximum-pagination-limit",level:3},{value:"Disabling Pagination",id:"disabling-pagination",level:3},{value:"Project-Wide",id:"project-wide",level:4},{value:"Per Repository",id:"per-repository",level:4},{value:"RequestCriteria",id:"requestcriteria",level:2},{value:"Sorting & Ordering",id:"sorting--ordering",level:3},{value:"Searching",id:"searching",level:3},{value:"Searching with Hashed IDs",id:"searching-with-hashed-ids",level:4},{value:"Filtering",id:"filtering",level:3},{value:"Caching",id:"caching",level:2},{value:"Skipping Cache",id:"skipping-cache",level:3}],d={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato provides a powerful repository pattern implementation based on the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository")," package."),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"To prevent overlap with the L5 Repository documentation, this page\nexclusively delves into Apiato distinct features and configurations,\noffering only a limited set of examples.\nTo learn more about the L5 Repository package,\nplease refer to its ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"documentation"),".")),(0,i.kt)("p",null,"Repositories play a crucial role in software architecture\nby separating and abstracting the data layer from the business logic.\nThey serve as an essential architectural pattern to promote code separation,\nflexibility, and maintainability in software development.\nRepositories help\ncreate clean and organized codebases\nby abstracting the intricacies of data access and manipulation from the core business logic."),(0,i.kt)("p",null,"To generate new repositories\nyou may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:repository")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:repository\n")),(0,i.kt)("p",null,"You can also generate a model and its repository at the same time by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:model")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:model\n")),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All Repositories:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Repositories\\Repository")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRepository"),"."))),(0,i.kt)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository.php"),"."))),(0,i.kt)("li",{parentName:"ul"},"A Model MUST always get accessed through its Repository."),(0,i.kt)("li",{parentName:"ul"},"Every Model MUST have a Repository.")),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 Data\n \u2514\u2500\u2500 Repositories\n \u251c\u2500\u2500 UserRepository.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass UserRepository extends ParentRepository\n{\n protected $fieldSearchable = [\n 'id' => '=',\n 'name' => 'like',\n 'email' => '=',\n 'email_verified_at' => '=',\n 'created_at' => '=',\n ];\n}\n")),(0,i.kt)("h2",{id:"configuration"},"Configuration"),(0,i.kt)("p",null,"All the configuration options for the this feature are located in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config file."),(0,i.kt)("h2",{id:"linking-repositories--models"},"Linking Repositories & Models"),(0,i.kt)("p",null,"Once you have created a repository, you need to link it to its corresponding model.\nIf you have followed the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nApiato will automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"However, if you have not followed the rules, and your repository name differs from the model name,\nyou must implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"model")," method in the repository."),(0,i.kt)("p",null,"This method allows you to specify the correct model class that should be used for the repository operations."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass DemoRepository extends ParentRepository\n{\n // ...\n \n public function model(): string\n {\n return AnotherDemo::class;\n }\n}\n")),(0,i.kt)("h3",{id:"repositories--models-auto-linking"},"Repositories & Models Auto-Linking"),(0,i.kt)("p",null,"Apiato offers a repository auto-linking feature that eliminates the need for manual linking of repositories & models.\nThis automatic linking process relies on adhering to standard Apiato naming conventions for repositories."),(0,i.kt)("p",null,"By following the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nyou allow Apiato to automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"To summarize:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Repositories must be stored within the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"The repository name should mirror the corresponding model's name while appending a ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, a ",(0,i.kt)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository")," repository class.")),(0,i.kt)("h2",{id:"pagination"},"Pagination"),(0,i.kt)("p",null,"Pagination is automatically applied when you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"paginate")," method with a model repository:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"{\n $this->userRepository->paginate();\n}\n")),(0,i.kt)("p",null,"To move between pages of results, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?page=200\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," parameter in any ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," request that lists records."),(0,i.kt)("p",null,"Whenever a request returns paginated results,\nyou'll find information about the pagination in the ",(0,i.kt)("strong",{parentName:"p"},"meta")," section of the response,\nwhich tells you things like the total number of records, the number on the current page, and more."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'"data": [...],\n"meta": {\n "pagination": {\n "total": 2000,\n "count": 30,\n "per_page": 10,\n "current_page": 20,\n "total_pages": 200,\n "links": []\n }\n}\n')),(0,i.kt)("h3",{id:"limiting-results-per-page"},"Limiting Results Per Page"),(0,i.kt)("p",null,"You can control the number of results displayed on a single page using the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," parameter."),(0,i.kt)("p",null,"By default, the pagination limit is set to 10.\nIf you don't specify the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=")," parameter in your request, the response will contain 10 records per page."),(0,i.kt)("p",null,"You can adjust the default pagination limit in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"PAGINATION_LIMIT_DEFAULT=10\n")),(0,i.kt)("p",null,"For instance, if you want to receive 100 resources per page, add the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=100")," query parameter to your request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100\n")),(0,i.kt)("p",null,"This will return 100 resources within a single page of results.\nYou can also combine the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameters to access the next 100 resources:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100&page=2\n")),(0,i.kt)("h3",{id:"maximum-pagination-limit"},"Maximum Pagination Limit"),(0,i.kt)("p",null,"You can also set the maximum number of resources\nthat can be returned in a single page by setting the ",(0,i.kt)("inlineCode",{parentName:"p"},"maxPaginationLimit")," property in your repository class."),(0,i.kt)("p",null,"For example, to set the maximum number of resources to 20, you can do the following:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $maxPaginationLimit = 20;\n")),(0,i.kt)("h3",{id:"disabling-pagination"},"Disabling Pagination"),(0,i.kt)("p",null,"You can allow clients to request all data that matches their criteria and disable pagination,\nwhich can be applied either project-wide or on a per-repository basis.\nThis enables a request to retrieve all matching data by specifying ",(0,i.kt)("inlineCode",{parentName:"p"},"limit=0"),"."),(0,i.kt)("p",null,"To retrieve all matching entities in a single page, you can use the following query parameter:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=0\n")),(0,i.kt)("h4",{id:"project-wide"},"Project-Wide"),(0,i.kt)("p",null,"To allow disabling pagination for the entire project, set ",(0,i.kt)("inlineCode",{parentName:"p"},"PAGINATION_SKIP=true")," in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("h4",{id:"per-repository"},"Per Repository"),(0,i.kt)("p",null,"If you wish to allow disabling pagination for a specific repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"$allowDisablePagination")," property in your repository class."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Per-repository configurations take precedence and override the global settings.")),(0,i.kt)("h2",{id:"requestcriteria"},"RequestCriteria"),(0,i.kt)("p",null,"RequestCriteria is a standard Criteria implementation\nthat enables filters to be applied to the repository based on parameters sent in the request.\nIt allows for dynamic searches, data filtering, and query customization."),(0,i.kt)("p",null,"To utilize RequestCriteria, you need to apply it to the repository.\nApiato provides two methods, ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria"),", which are available on all repositories."),(0,i.kt)("p",null,"Here's an example of how to use RequestCriteria:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Containers\\AppSection\\User\\Data\\Repositories\\UserRepository;\nuse App\\Ship\\Parents\\Tasks\\Task as ParentTask;\n\nclass GetAllUsersTask extends ParentTask\n{\n public function __construct(\n protected readonly UserRepository $repository\n ) {\n }\n\n public function run(): mixed\n {\n // $this->repository->removeRequestCriteria();\n return $this->repository->addRequestCriteria()->paginate();\n }\n}\n")),(0,i.kt)("p",null,"It's important to note\nthat using the ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria")," method is only relevant if you have previously applied ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria"),".\nIf it hasn't been applied, there is no need to remove it,\nas RequestCriteria is not automatically applied unless explicitly used."),(0,i.kt)("h3",{id:"sorting--ordering"},"Sorting & Ordering"),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameters to sort the data in the response."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter is typically employed in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter."),(0,i.kt)("p",null,"By default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter sorts the data in ",(0,i.kt)("strong",{parentName:"p"},"Ascending")," order.\nTo sort the data in ",(0,i.kt)("strong",{parentName:"p"},"Descending")," order, you can include ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy=desc"),"."),(0,i.kt)("p",null,"For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=id&sortedBy=asc\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=created_at&sortedBy=desc\n?orderBy=name&sortedBy=asc\n")),(0,i.kt)("p",null,"You can use the following values for the ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"asc")," for Ascending."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"desc")," for Descending.")),(0,i.kt)("h3",{id:"searching"},"Searching"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"There is much more to searching than what is covered here.\nPlease refer to the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository documentation")," for more details.")),(0,i.kt)("p",null,"When the RequestCriteria is enabled for a specific route,\nyou can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," HTTP requests on that route to perform searches."),(0,i.kt)("p",null,"To make the search feature work, you need to specify which fields from the model should be searchable.\nIn your repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"fieldSearchable")," field with the names of the fields\nyou want to make searchable or specify a relation to the fields."),(0,i.kt)("p",null,"Here's an example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name',\n 'email',\n 'product.name'\n];\n")),(0,i.kt)("p",null,'You can also set the type of condition that will be used to perform the query. The default condition is "=":'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name' => 'like',\n 'email', // Default Condition \"=\"\n 'your_field' => 'condition'\n];\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in various ways:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?search=John\n?search=name:John\n?search=name:John%20Doe\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Replace spaces with ",(0,i.kt)("inlineCode",{parentName:"p"},"%20")," in the URL (e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"search=keyword%20here"),").")),(0,i.kt)("h4",{id:"searching-with-hashed-ids"},"Searching with Hashed IDs"),(0,i.kt)("p",null,"If you have ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," enabled and want to search a hashed field,\nlike a user ID, you need to instruct the ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria")," to decode it before performing the search."),(0,i.kt)("p",null,"For example,\nif you have a search query like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"`?search=id:XbPW7awNkzl83LD6;parent_id:aYOxlpzRMwrX3gD7;some_hashed_id:NxOpZowo9GmjKqdR`\n")),(0,i.kt)("p",null,"You should update your ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," method as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->repository->addRequestCriteria(['id', 'parent_id', 'some_hashed_id'])->all();\n")),(0,i.kt)("h3",{id:"filtering"},"Filtering"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter can be used in any HTTP request to control the response size by specifying the data you want in the response."),(0,i.kt)("p",null,"For example, to retrieve only the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," fields from a Model, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?filter=id;status\n")),(0,i.kt)("p",null,"The response will include only the specified fields, as shown in the example response:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved"\n }\n ]\n}\n')),(0,i.kt)("p",null,"It's important to note that the transformer used to format the data is also filtered.\nThis means that only the fields specified in the filter are present, and all other fields are excluded.\nThis filtering also applies to all ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/transformers#including-relationships"},"included relationships")," of the object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved",\n "products": {\n "data": [\n {\n "id": "bmo7y84xpgeza06k",\n "status": "pending"\n },\n {\n "id": "o0wzxbg0q4k7jp9d",\n "status": "fulfilled"\n }\n ]\n },\n "recipients": {\n "data": [\n {\n "id": "r6lbekg8rv5ozyad"\n }\n ]\n },\n "store": {\n "data": {\n "id": "r6lbekg8rv5ozyad"\n }\n }\n }\n ]\n}\n')),(0,i.kt)("h2",{id:"caching"},"Caching"),(0,i.kt)("p",null,"L5 Repository supports caching of queries.\nThis feature is ",(0,i.kt)("inlineCode",{parentName:"p"},"disabled")," by default.\nYou can enable caching in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config or the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'cache' => [\n 'enabled' => true,\n],\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"ELOQUENT_QUERY_CACHE=true\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"As per the L5 Repository documentation,\nyou need to implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepository")," interface\nand use the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepositoryTrait")," trait in your repository class to enable caching.\nHowever, Apiato already implements these two requirements in the parent ",(0,i.kt)("inlineCode",{parentName:"p"},"Repository")," class,")),(0,i.kt)("h3",{id:"skipping-cache"},"Skipping Cache"),(0,i.kt)("p",null,"You can skip the cache for a specific request by adding the ",(0,i.kt)("inlineCode",{parentName:"p"},"?skipCache=true")," query parameter to the request URL."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?skipCache=true\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"It's not recommended to skip the cache, but it's useful for testing purposes.")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8995],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),s=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},d=function(e){var t=s(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=s(n),m=i,h=u["".concat(l,".").concat(m)]||u[m]||c[m]||o;return n?a.createElement(h,r(r({ref:t},d),{},{components:n})):a.createElement(h,r({ref:t},d))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,r=new Array(o);r[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p[u]="string"==typeof e?e:i,r[1]=p;for(var s=2;s{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var a=n(87462),i=(n(67294),n(3905));const o={sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},r=void 0,p={unversionedId:"components/optional-components/repository/repositories",id:"components/optional-components/repository/repositories",title:"Repositories",description:"Apiato provides a powerful repository pattern implementation based on the L5 Repository package.",source:"@site/docs/components/optional-components/repository/repositories.md",sourceDirName:"components/optional-components/repository",slug:"/components/optional-components/repository/repositories",permalink:"/docs/next/components/optional-components/repository/repositories",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/repository/repositories.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"repository",permalink:"/docs/next/tags/repository"},{label:"criteria",permalink:"/docs/next/tags/criteria"},{label:"action",permalink:"/docs/next/tags/action"},{label:"task",permalink:"/docs/next/tags/task"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},sidebar:"tutorialSidebar",previous:{title:"Policies",permalink:"/docs/next/components/optional-components/policies"},next:{title:"Criterias",permalink:"/docs/next/components/optional-components/repository/criterias"}},l={},s=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Linking Repositories & Models",id:"linking-repositories--models",level:2},{value:"Repositories & Models Auto-Linking",id:"repositories--models-auto-linking",level:3},{value:"Pagination",id:"pagination",level:2},{value:"Limiting Results Per Page",id:"limiting-results-per-page",level:3},{value:"Maximum Pagination Limit",id:"maximum-pagination-limit",level:3},{value:"Disabling Pagination",id:"disabling-pagination",level:3},{value:"Project-Wide",id:"project-wide",level:4},{value:"Per Repository",id:"per-repository",level:4},{value:"RequestCriteria",id:"requestcriteria",level:2},{value:"Sorting & Ordering",id:"sorting--ordering",level:3},{value:"Searching",id:"searching",level:3},{value:"Searching with Hashed IDs",id:"searching-with-hashed-ids",level:4},{value:"Filtering",id:"filtering",level:3},{value:"Caching",id:"caching",level:2},{value:"Skipping Cache",id:"skipping-cache",level:3}],d={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato provides a powerful repository pattern implementation based on the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository")," package."),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"To prevent overlap with the L5 Repository documentation, this page\nexclusively delves into Apiato distinct features and configurations,\noffering only a limited set of examples.\nTo learn more about the L5 Repository package,\nplease refer to its ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"documentation"),".")),(0,i.kt)("p",null,"Repositories play a crucial role in software architecture\nby separating and abstracting the data layer from the business logic.\nThey serve as an essential architectural pattern to promote code separation,\nflexibility, and maintainability in software development.\nRepositories help\ncreate clean and organized codebases\nby abstracting the intricacies of data access and manipulation from the core business logic."),(0,i.kt)("p",null,"To generate new repositories\nyou may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:repository")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:repository\n")),(0,i.kt)("p",null,"You can also generate a model and its repository at the same time by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:model")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:model\n")),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All Repositories:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Repositories\\Repository")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRepository"),"."))),(0,i.kt)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository.php"),"."))),(0,i.kt)("li",{parentName:"ul"},"A Model MUST always get accessed through its Repository."),(0,i.kt)("li",{parentName:"ul"},"Every Model MUST have a Repository.")),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 Data\n \u2514\u2500\u2500 Repositories\n \u251c\u2500\u2500 UserRepository.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass UserRepository extends ParentRepository\n{\n protected $fieldSearchable = [\n 'id' => '=',\n 'name' => 'like',\n 'email' => '=',\n 'email_verified_at' => '=',\n 'created_at' => '=',\n ];\n}\n")),(0,i.kt)("h2",{id:"configuration"},"Configuration"),(0,i.kt)("p",null,"All the configuration options for the this feature are located in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config file."),(0,i.kt)("h2",{id:"linking-repositories--models"},"Linking Repositories & Models"),(0,i.kt)("p",null,"Once you have created a repository, you need to link it to its corresponding model.\nIf you have followed the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nApiato will automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"However, if you have not followed the rules, and your repository name differs from the model name,\nyou must implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"model")," method in the repository."),(0,i.kt)("p",null,"This method allows you to specify the correct model class that should be used for the repository operations."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass DemoRepository extends ParentRepository\n{\n // ...\n \n public function model(): string\n {\n return AnotherDemo::class;\n }\n}\n")),(0,i.kt)("h3",{id:"repositories--models-auto-linking"},"Repositories & Models Auto-Linking"),(0,i.kt)("p",null,"Apiato offers a repository auto-linking feature that eliminates the need for manual linking of repositories & models.\nThis automatic linking process relies on adhering to standard Apiato naming conventions for repositories."),(0,i.kt)("p",null,"By following the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nyou allow Apiato to automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"To summarize:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Repositories must be stored within the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"The repository name should mirror the corresponding model's name while appending a ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, a ",(0,i.kt)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository")," repository class.")),(0,i.kt)("h2",{id:"pagination"},"Pagination"),(0,i.kt)("p",null,"Pagination is automatically applied when you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"paginate")," method with a model repository:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"{\n $this->userRepository->paginate();\n}\n")),(0,i.kt)("p",null,"To move between pages of results, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?page=200\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," parameter in any ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," request that lists records."),(0,i.kt)("p",null,"Whenever a request returns paginated results,\nyou'll find information about the pagination in the ",(0,i.kt)("strong",{parentName:"p"},"meta")," section of the response,\nwhich tells you things like the total number of records, the number on the current page, and more."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'"data": [...],\n"meta": {\n "pagination": {\n "total": 2000,\n "count": 30,\n "per_page": 10,\n "current_page": 20,\n "total_pages": 200,\n "links": []\n }\n}\n')),(0,i.kt)("h3",{id:"limiting-results-per-page"},"Limiting Results Per Page"),(0,i.kt)("p",null,"You can control the number of results displayed on a single page using the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," parameter."),(0,i.kt)("p",null,"By default, the pagination limit is set to 10.\nIf you don't specify the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=")," parameter in your request, the response will contain 10 records per page."),(0,i.kt)("p",null,"You can adjust the default pagination limit in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"PAGINATION_LIMIT_DEFAULT=10\n")),(0,i.kt)("p",null,"For instance, if you want to receive 100 resources per page, add the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=100")," query parameter to your request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100\n")),(0,i.kt)("p",null,"This will return 100 resources within a single page of results.\nYou can also combine the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameters to access the next 100 resources:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100&page=2\n")),(0,i.kt)("h3",{id:"maximum-pagination-limit"},"Maximum Pagination Limit"),(0,i.kt)("p",null,"You can also set the maximum number of resources\nthat can be returned in a single page by setting the ",(0,i.kt)("inlineCode",{parentName:"p"},"$maxPaginationLimit")," property in your repository class."),(0,i.kt)("p",null,"For example, to set the maximum number of resources to 20, you can do the following:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $maxPaginationLimit = 20;\n")),(0,i.kt)("h3",{id:"disabling-pagination"},"Disabling Pagination"),(0,i.kt)("p",null,"You can allow clients to request all data that matches their criteria and disable pagination,\nwhich can be applied either project-wide or on a per-repository basis.\nThis enables a request to retrieve all matching data by specifying ",(0,i.kt)("inlineCode",{parentName:"p"},"limit=0"),"."),(0,i.kt)("p",null,"To retrieve all matching entities in a single page, you can use the following query parameter:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=0\n")),(0,i.kt)("h4",{id:"project-wide"},"Project-Wide"),(0,i.kt)("p",null,"To allow disabling pagination for the entire project, set ",(0,i.kt)("inlineCode",{parentName:"p"},"PAGINATION_SKIP=true")," in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("h4",{id:"per-repository"},"Per Repository"),(0,i.kt)("p",null,"If you wish to allow disabling pagination for a specific repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"$allowDisablePagination")," property in your repository class."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Per-repository configurations take precedence and override the global settings.")),(0,i.kt)("h2",{id:"requestcriteria"},"RequestCriteria"),(0,i.kt)("p",null,"RequestCriteria is a standard Criteria implementation\nthat enables filters to be applied to the repository based on parameters sent in the request.\nIt allows for dynamic searches, data filtering, and query customization."),(0,i.kt)("p",null,"To utilize RequestCriteria, you need to apply it to the repository.\nApiato provides two methods, ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria"),", which are available on all repositories."),(0,i.kt)("p",null,"Here's an example of how to use RequestCriteria:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Containers\\AppSection\\User\\Data\\Repositories\\UserRepository;\nuse App\\Ship\\Parents\\Tasks\\Task as ParentTask;\n\nclass GetAllUsersTask extends ParentTask\n{\n public function __construct(\n protected readonly UserRepository $repository\n ) {\n }\n\n public function run(): mixed\n {\n // $this->repository->removeRequestCriteria();\n return $this->repository->addRequestCriteria()->paginate();\n }\n}\n")),(0,i.kt)("p",null,"It's important to note\nthat using the ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria")," method is only relevant if you have previously applied ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria"),".\nIf it hasn't been applied, there is no need to remove it,\nas RequestCriteria is not automatically applied unless explicitly used."),(0,i.kt)("h3",{id:"sorting--ordering"},"Sorting & Ordering"),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameters to sort the data in the response."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter is typically employed in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter."),(0,i.kt)("p",null,"By default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter sorts the data in ",(0,i.kt)("strong",{parentName:"p"},"Ascending")," order.\nTo sort the data in ",(0,i.kt)("strong",{parentName:"p"},"Descending")," order, you can include ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy=desc"),"."),(0,i.kt)("p",null,"For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=id&sortedBy=asc\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=created_at&sortedBy=desc\n?orderBy=name&sortedBy=asc\n")),(0,i.kt)("p",null,"You can use the following values for the ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"asc")," for Ascending."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"desc")," for Descending.")),(0,i.kt)("h3",{id:"searching"},"Searching"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"There is much more to searching than what is covered here.\nPlease refer to the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository documentation")," for more details.")),(0,i.kt)("p",null,"When the RequestCriteria is enabled for a specific route,\nyou can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," HTTP requests on that route to perform searches."),(0,i.kt)("p",null,"To make the search feature work, you need to specify which fields from the model should be searchable.\nIn your repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"fieldSearchable")," field with the names of the fields\nyou want to make searchable or specify a relation to the fields."),(0,i.kt)("p",null,"Here's an example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name',\n 'email',\n 'product.name'\n];\n")),(0,i.kt)("p",null,'You can also set the type of condition that will be used to perform the query. The default condition is "=":'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name' => 'like',\n 'email', // Default Condition \"=\"\n 'your_field' => 'condition'\n];\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in various ways:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?search=John\n?search=name:John\n?search=name:John%20Doe\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Replace spaces with ",(0,i.kt)("inlineCode",{parentName:"p"},"%20")," in the URL (e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"search=keyword%20here"),").")),(0,i.kt)("h4",{id:"searching-with-hashed-ids"},"Searching with Hashed IDs"),(0,i.kt)("p",null,"If you have ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," enabled and want to search a hashed field,\nlike a user ID, you need to instruct the ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria")," to decode it before performing the search."),(0,i.kt)("p",null,"For example,\nif you have a search query like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"`?search=id:XbPW7awNkzl83LD6;parent_id:aYOxlpzRMwrX3gD7;some_hashed_id:NxOpZowo9GmjKqdR`\n")),(0,i.kt)("p",null,"You should update your ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," method as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->repository->addRequestCriteria(['id', 'parent_id', 'some_hashed_id'])->all();\n")),(0,i.kt)("h3",{id:"filtering"},"Filtering"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter can be used in any HTTP request to control the response size by specifying the data you want in the response."),(0,i.kt)("p",null,"For example, to retrieve only the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," fields from a Model, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?filter=id;status\n")),(0,i.kt)("p",null,"The response will include only the specified fields, as shown in the example response:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved"\n }\n ]\n}\n')),(0,i.kt)("p",null,"It's important to note that the transformer used to format the data is also filtered.\nThis means that only the fields specified in the filter are present, and all other fields are excluded.\nThis filtering also applies to all ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/transformers#including-relationships"},"included relationships")," of the object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved",\n "products": {\n "data": [\n {\n "id": "bmo7y84xpgeza06k",\n "status": "pending"\n },\n {\n "id": "o0wzxbg0q4k7jp9d",\n "status": "fulfilled"\n }\n ]\n },\n "recipients": {\n "data": [\n {\n "id": "r6lbekg8rv5ozyad"\n }\n ]\n },\n "store": {\n "data": {\n "id": "r6lbekg8rv5ozyad"\n }\n }\n }\n ]\n}\n')),(0,i.kt)("h2",{id:"caching"},"Caching"),(0,i.kt)("p",null,"L5 Repository supports caching of queries.\nThis feature is ",(0,i.kt)("inlineCode",{parentName:"p"},"disabled")," by default.\nYou can enable caching in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config or the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'cache' => [\n 'enabled' => true,\n],\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"ELOQUENT_QUERY_CACHE=true\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"As per the L5 Repository documentation,\nyou need to implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepository")," interface\nand use the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepositoryTrait")," trait in your repository class to enable caching.\nHowever, Apiato already implements these two requirements in the parent ",(0,i.kt)("inlineCode",{parentName:"p"},"Repository")," class,")),(0,i.kt)("h3",{id:"skipping-cache"},"Skipping Cache"),(0,i.kt)("p",null,"You can skip the cache for a specific request by adding the ",(0,i.kt)("inlineCode",{parentName:"p"},"?skipCache=true")," query parameter to the request URL."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?skipCache=true\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"It's not recommended to skip the cache, but it's useful for testing purposes.")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8e61a622.6399ba3b.js b/assets/js/8e61a622.ca2121f9.js similarity index 58% rename from assets/js/8e61a622.6399ba3b.js rename to assets/js/8e61a622.ca2121f9.js index ab459102e..55ae7aa20 100644 --- a/assets/js/8e61a622.6399ba3b.js +++ b/assets/js/8e61a622.ca2121f9.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2818],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),s=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},d=function(e){var t=s(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=s(n),m=i,h=u["".concat(l,".").concat(m)]||u[m]||c[m]||o;return n?a.createElement(h,r(r({ref:t},d),{},{components:n})):a.createElement(h,r({ref:t},d))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,r=new Array(o);r[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p[u]="string"==typeof e?e:i,r[1]=p;for(var s=2;s{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var a=n(87462),i=(n(67294),n(3905));const o={sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},r=void 0,p={unversionedId:"components/optional-components/repository/repositories",id:"version-12.x/components/optional-components/repository/repositories",title:"Repositories",description:"Apiato provides a powerful repository pattern implementation based on the L5 Repository package.",source:"@site/versioned_docs/version-12.x/components/optional-components/repository/repositories.md",sourceDirName:"components/optional-components/repository",slug:"/components/optional-components/repository/repositories",permalink:"/docs/components/optional-components/repository/repositories",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/repository/repositories.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"repository",permalink:"/docs/tags/repository"},{label:"criteria",permalink:"/docs/tags/criteria"},{label:"action",permalink:"/docs/tags/action"},{label:"task",permalink:"/docs/tags/task"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697552040,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},sidebar:"tutorialSidebar",previous:{title:"Policies",permalink:"/docs/components/optional-components/policies"},next:{title:"Criterias",permalink:"/docs/components/optional-components/repository/criterias"}},l={},s=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Linking Repositories & Models",id:"linking-repositories--models",level:2},{value:"Repositories & Models Auto-Linking",id:"repositories--models-auto-linking",level:3},{value:"Pagination",id:"pagination",level:2},{value:"Limiting Results Per Page",id:"limiting-results-per-page",level:3},{value:"Maximum Pagination Limit",id:"maximum-pagination-limit",level:3},{value:"Disabling Pagination",id:"disabling-pagination",level:3},{value:"Project-Wide",id:"project-wide",level:4},{value:"Per Repository",id:"per-repository",level:4},{value:"RequestCriteria",id:"requestcriteria",level:2},{value:"Sorting & Ordering",id:"sorting--ordering",level:3},{value:"Searching",id:"searching",level:3},{value:"Searching with Hashed IDs",id:"searching-with-hashed-ids",level:4},{value:"Filtering",id:"filtering",level:3},{value:"Caching",id:"caching",level:2},{value:"Skipping Cache",id:"skipping-cache",level:3}],d={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato provides a powerful repository pattern implementation based on the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository")," package."),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"To prevent overlap with the L5 Repository documentation, this page\nexclusively delves into Apiato distinct features and configurations,\noffering only a limited set of examples.\nTo learn more about the L5 Repository package,\nplease refer to its ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"documentation"),".")),(0,i.kt)("p",null,"Repositories play a crucial role in software architecture\nby separating and abstracting the data layer from the business logic.\nThey serve as an essential architectural pattern to promote code separation,\nflexibility, and maintainability in software development.\nRepositories help\ncreate clean and organized codebases\nby abstracting the intricacies of data access and manipulation from the core business logic."),(0,i.kt)("p",null,"To generate new repositories\nyou may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:repository")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:repository\n")),(0,i.kt)("p",null,"You can also generate a model and its repository at the same time by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:model")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:model\n")),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All Repositories:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Repositories\\Repository")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRepository"),"."))),(0,i.kt)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository.php"),"."))),(0,i.kt)("li",{parentName:"ul"},"A Model MUST always get accessed through its Repository."),(0,i.kt)("li",{parentName:"ul"},"Every Model MUST have a Repository.")),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 Data\n \u2514\u2500\u2500 Repositories\n \u251c\u2500\u2500 UserRepository.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass UserRepository extends ParentRepository\n{\n protected $fieldSearchable = [\n 'id' => '=',\n 'name' => 'like',\n 'email' => '=',\n 'email_verified_at' => '=',\n 'created_at' => '=',\n ];\n}\n")),(0,i.kt)("h2",{id:"configuration"},"Configuration"),(0,i.kt)("p",null,"All the configuration options for the this feature are located in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config file."),(0,i.kt)("h2",{id:"linking-repositories--models"},"Linking Repositories & Models"),(0,i.kt)("p",null,"Once you have created a repository, you need to link it to its corresponding model.\nIf you have followed the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nApiato will automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"However, if you have not followed the rules, and your repository name differs from the model name,\nyou must implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"model")," method in the repository."),(0,i.kt)("p",null,"This method allows you to specify the correct model class that should be used for the repository operations."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass DemoRepository extends ParentRepository\n{\n // ...\n \n public function model(): string\n {\n return AnotherDemo::class;\n }\n}\n")),(0,i.kt)("h3",{id:"repositories--models-auto-linking"},"Repositories & Models Auto-Linking"),(0,i.kt)("p",null,"Apiato offers a repository auto-linking feature that eliminates the need for manual linking of repositories & models.\nThis automatic linking process relies on adhering to standard Apiato naming conventions for repositories."),(0,i.kt)("p",null,"By following the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nyou allow Apiato to automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"To summarize:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Repositories must be stored within the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"The repository name should mirror the corresponding model's name while appending a ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, a ",(0,i.kt)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository")," repository class.")),(0,i.kt)("h2",{id:"pagination"},"Pagination"),(0,i.kt)("p",null,"Pagination is automatically applied when you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"paginate")," method with a model repository:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"{\n $this->userRepository->paginate();\n}\n")),(0,i.kt)("p",null,"To move between pages of results, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?page=200\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," parameter in any ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," request that lists records."),(0,i.kt)("p",null,"Whenever a request returns paginated results,\nyou'll find information about the pagination in the ",(0,i.kt)("strong",{parentName:"p"},"meta")," section of the response,\nwhich tells you things like the total number of records, the number on the current page, and more."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'"data": [...],\n"meta": {\n "pagination": {\n "total": 2000,\n "count": 30,\n "per_page": 10,\n "current_page": 20,\n "total_pages": 200,\n "links": []\n }\n}\n')),(0,i.kt)("h3",{id:"limiting-results-per-page"},"Limiting Results Per Page"),(0,i.kt)("p",null,"You can control the number of results displayed on a single page using the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," parameter."),(0,i.kt)("p",null,"By default, the pagination limit is set to 10.\nIf you don't specify the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=")," parameter in your request, the response will contain 10 records per page."),(0,i.kt)("p",null,"You can adjust the default pagination limit in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"PAGINATION_LIMIT_DEFAULT=10\n")),(0,i.kt)("p",null,"For instance, if you want to receive 100 resources per page, add the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=100")," query parameter to your request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100\n")),(0,i.kt)("p",null,"This will return 100 resources within a single page of results.\nYou can also combine the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameters to access the next 100 resources:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100&page=2\n")),(0,i.kt)("h3",{id:"maximum-pagination-limit"},"Maximum Pagination Limit"),(0,i.kt)("p",null,"You can also set the maximum number of resources\nthat can be returned in a single page by setting the ",(0,i.kt)("inlineCode",{parentName:"p"},"maxPaginationLimit")," property in your repository class."),(0,i.kt)("p",null,"For example, to set the maximum number of resources to 20, you can do the following:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $maxPaginationLimit = 20;\n")),(0,i.kt)("h3",{id:"disabling-pagination"},"Disabling Pagination"),(0,i.kt)("p",null,"You can allow clients to request all data that matches their criteria and disable pagination,\nwhich can be applied either project-wide or on a per-repository basis.\nThis enables a request to retrieve all matching data by specifying ",(0,i.kt)("inlineCode",{parentName:"p"},"limit=0"),"."),(0,i.kt)("p",null,"To retrieve all matching entities in a single page, you can use the following query parameter:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=0\n")),(0,i.kt)("h4",{id:"project-wide"},"Project-Wide"),(0,i.kt)("p",null,"To allow disabling pagination for the entire project, set ",(0,i.kt)("inlineCode",{parentName:"p"},"PAGINATION_SKIP=true")," in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("h4",{id:"per-repository"},"Per Repository"),(0,i.kt)("p",null,"If you wish to allow disabling pagination for a specific repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"$allowDisablePagination")," property in your repository class."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Per-repository configurations take precedence and override the global settings.")),(0,i.kt)("h2",{id:"requestcriteria"},"RequestCriteria"),(0,i.kt)("p",null,"RequestCriteria is a standard Criteria implementation\nthat enables filters to be applied to the repository based on parameters sent in the request.\nIt allows for dynamic searches, data filtering, and query customization."),(0,i.kt)("p",null,"To utilize RequestCriteria, you need to apply it to the repository.\nApiato provides two methods, ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria"),", which are available on all repositories."),(0,i.kt)("p",null,"Here's an example of how to use RequestCriteria:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Containers\\AppSection\\User\\Data\\Repositories\\UserRepository;\nuse App\\Ship\\Parents\\Tasks\\Task as ParentTask;\n\nclass GetAllUsersTask extends ParentTask\n{\n public function __construct(\n protected readonly UserRepository $repository\n ) {\n }\n\n public function run(): mixed\n {\n // $this->repository->removeRequestCriteria();\n return $this->repository->addRequestCriteria()->paginate();\n }\n}\n")),(0,i.kt)("p",null,"It's important to note\nthat using the ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria")," method is only relevant if you have previously applied ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria"),".\nIf it hasn't been applied, there is no need to remove it,\nas RequestCriteria is not automatically applied unless explicitly used."),(0,i.kt)("h3",{id:"sorting--ordering"},"Sorting & Ordering"),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameters to sort the data in the response."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter is typically employed in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter."),(0,i.kt)("p",null,"By default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter sorts the data in ",(0,i.kt)("strong",{parentName:"p"},"Ascending")," order.\nTo sort the data in ",(0,i.kt)("strong",{parentName:"p"},"Descending")," order, you can include ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy=desc"),"."),(0,i.kt)("p",null,"For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=id&sortedBy=asc\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=created_at&sortedBy=desc\n?orderBy=name&sortedBy=asc\n")),(0,i.kt)("p",null,"You can use the following values for the ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"asc")," for Ascending."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"desc")," for Descending.")),(0,i.kt)("h3",{id:"searching"},"Searching"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"There is much more to searching than what is covered here.\nPlease refer to the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository documentation")," for more details.")),(0,i.kt)("p",null,"When the RequestCriteria is enabled for a specific route,\nyou can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," HTTP requests on that route to perform searches."),(0,i.kt)("p",null,"To make the search feature work, you need to specify which fields from the model should be searchable.\nIn your repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"fieldSearchable")," field with the names of the fields\nyou want to make searchable or specify a relation to the fields."),(0,i.kt)("p",null,"Here's an example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name',\n 'email',\n 'product.name'\n];\n")),(0,i.kt)("p",null,'You can also set the type of condition that will be used to perform the query. The default condition is "=":'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name' => 'like',\n 'email', // Default Condition \"=\"\n 'your_field' => 'condition'\n];\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in various ways:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?search=John\n?search=name:John\n?search=name:John%20Doe\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Replace spaces with ",(0,i.kt)("inlineCode",{parentName:"p"},"%20")," in the URL (e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"search=keyword%20here"),").")),(0,i.kt)("h4",{id:"searching-with-hashed-ids"},"Searching with Hashed IDs"),(0,i.kt)("p",null,"If you have ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," enabled and want to search a hashed field,\nlike a user ID, you need to instruct the ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria")," to decode it before performing the search."),(0,i.kt)("p",null,"For example,\nif you have a search query like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"`?search=id:XbPW7awNkzl83LD6;parent_id:aYOxlpzRMwrX3gD7;some_hashed_id:NxOpZowo9GmjKqdR`\n")),(0,i.kt)("p",null,"You should update your ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," method as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->repository->addRequestCriteria(['id', 'parent_id', 'some_hashed_id'])->all();\n")),(0,i.kt)("h3",{id:"filtering"},"Filtering"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter can be used in any HTTP request to control the response size by specifying the data you want in the response."),(0,i.kt)("p",null,"For example, to retrieve only the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," fields from a Model, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?filter=id;status\n")),(0,i.kt)("p",null,"The response will include only the specified fields, as shown in the example response:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved"\n }\n ]\n}\n')),(0,i.kt)("p",null,"It's important to note that the transformer used to format the data is also filtered.\nThis means that only the fields specified in the filter are present, and all other fields are excluded.\nThis filtering also applies to all ",(0,i.kt)("a",{parentName:"p",href:"/docs/components/main-components/transformers#including-relationships"},"included relationships")," of the object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved",\n "products": {\n "data": [\n {\n "id": "bmo7y84xpgeza06k",\n "status": "pending"\n },\n {\n "id": "o0wzxbg0q4k7jp9d",\n "status": "fulfilled"\n }\n ]\n },\n "recipients": {\n "data": [\n {\n "id": "r6lbekg8rv5ozyad"\n }\n ]\n },\n "store": {\n "data": {\n "id": "r6lbekg8rv5ozyad"\n }\n }\n }\n ]\n}\n')),(0,i.kt)("h2",{id:"caching"},"Caching"),(0,i.kt)("p",null,"L5 Repository supports caching of queries.\nThis feature is ",(0,i.kt)("inlineCode",{parentName:"p"},"disabled")," by default.\nYou can enable caching in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config or the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'cache' => [\n 'enabled' => true,\n],\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"ELOQUENT_QUERY_CACHE=true\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"As per the L5 Repository documentation,\nyou need to implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepository")," interface\nand use the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepositoryTrait")," trait in your repository class to enable caching.\nHowever, Apiato already implements these two requirements in the parent ",(0,i.kt)("inlineCode",{parentName:"p"},"Repository")," class,")),(0,i.kt)("h3",{id:"skipping-cache"},"Skipping Cache"),(0,i.kt)("p",null,"You can skip the cache for a specific request by adding the ",(0,i.kt)("inlineCode",{parentName:"p"},"?skipCache=true")," query parameter to the request URL."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?skipCache=true\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"It's not recommended to skip the cache, but it's useful for testing purposes.")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2818],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>h});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),s=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},d=function(e){var t=s(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=s(n),m=i,h=u["".concat(l,".").concat(m)]||u[m]||c[m]||o;return n?a.createElement(h,r(r({ref:t},d),{},{components:n})):a.createElement(h,r({ref:t},d))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,r=new Array(o);r[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p[u]="string"==typeof e?e:i,r[1]=p;for(var s=2;s{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var a=n(87462),i=(n(67294),n(3905));const o={sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},r=void 0,p={unversionedId:"components/optional-components/repository/repositories",id:"version-12.x/components/optional-components/repository/repositories",title:"Repositories",description:"Apiato provides a powerful repository pattern implementation based on the L5 Repository package.",source:"@site/versioned_docs/version-12.x/components/optional-components/repository/repositories.md",sourceDirName:"components/optional-components/repository",slug:"/components/optional-components/repository/repositories",permalink:"/docs/components/optional-components/repository/repositories",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/repository/repositories.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"repository",permalink:"/docs/tags/repository"},{label:"criteria",permalink:"/docs/tags/criteria"},{label:"action",permalink:"/docs/tags/action"},{label:"task",permalink:"/docs/tags/task"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Repositories",tags:["component","optional-component","repository","criteria","action","task"]},sidebar:"tutorialSidebar",previous:{title:"Policies",permalink:"/docs/components/optional-components/policies"},next:{title:"Criterias",permalink:"/docs/components/optional-components/repository/criterias"}},l={},s=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Linking Repositories & Models",id:"linking-repositories--models",level:2},{value:"Repositories & Models Auto-Linking",id:"repositories--models-auto-linking",level:3},{value:"Pagination",id:"pagination",level:2},{value:"Limiting Results Per Page",id:"limiting-results-per-page",level:3},{value:"Maximum Pagination Limit",id:"maximum-pagination-limit",level:3},{value:"Disabling Pagination",id:"disabling-pagination",level:3},{value:"Project-Wide",id:"project-wide",level:4},{value:"Per Repository",id:"per-repository",level:4},{value:"RequestCriteria",id:"requestcriteria",level:2},{value:"Sorting & Ordering",id:"sorting--ordering",level:3},{value:"Searching",id:"searching",level:3},{value:"Searching with Hashed IDs",id:"searching-with-hashed-ids",level:4},{value:"Filtering",id:"filtering",level:3},{value:"Caching",id:"caching",level:2},{value:"Skipping Cache",id:"skipping-cache",level:3}],d={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato provides a powerful repository pattern implementation based on the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository")," package."),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"To prevent overlap with the L5 Repository documentation, this page\nexclusively delves into Apiato distinct features and configurations,\noffering only a limited set of examples.\nTo learn more about the L5 Repository package,\nplease refer to its ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"documentation"),".")),(0,i.kt)("p",null,"Repositories play a crucial role in software architecture\nby separating and abstracting the data layer from the business logic.\nThey serve as an essential architectural pattern to promote code separation,\nflexibility, and maintainability in software development.\nRepositories help\ncreate clean and organized codebases\nby abstracting the intricacies of data access and manipulation from the core business logic."),(0,i.kt)("p",null,"To generate new repositories\nyou may use the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:repository")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:repository\n")),(0,i.kt)("p",null,"You can also generate a model and its repository at the same time by using the ",(0,i.kt)("inlineCode",{parentName:"p"},"apiato:generate:model")," interactive command:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:model\n")),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All Repositories:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Repositories\\Repository")," class.",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentRepository"),"."))),(0,i.kt)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository.php"),"."))),(0,i.kt)("li",{parentName:"ul"},"A Model MUST always get accessed through its Repository."),(0,i.kt)("li",{parentName:"ul"},"Every Model MUST have a Repository.")),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 Data\n \u2514\u2500\u2500 Repositories\n \u251c\u2500\u2500 UserRepository.php\n \u2514\u2500\u2500 ...\n")),(0,i.kt)("h2",{id:"code-example"},"Code Example"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass UserRepository extends ParentRepository\n{\n protected $fieldSearchable = [\n 'id' => '=',\n 'name' => 'like',\n 'email' => '=',\n 'email_verified_at' => '=',\n 'created_at' => '=',\n ];\n}\n")),(0,i.kt)("h2",{id:"configuration"},"Configuration"),(0,i.kt)("p",null,"All the configuration options for the this feature are located in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config file."),(0,i.kt)("h2",{id:"linking-repositories--models"},"Linking Repositories & Models"),(0,i.kt)("p",null,"Once you have created a repository, you need to link it to its corresponding model.\nIf you have followed the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nApiato will automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"However, if you have not followed the rules, and your repository name differs from the model name,\nyou must implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"model")," method in the repository."),(0,i.kt)("p",null,"This method allows you to specify the correct model class that should be used for the repository operations."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Ship\\Parents\\Repositories\\Repository as ParentRepository;\n\nclass DemoRepository extends ParentRepository\n{\n // ...\n \n public function model(): string\n {\n return AnotherDemo::class;\n }\n}\n")),(0,i.kt)("h3",{id:"repositories--models-auto-linking"},"Repositories & Models Auto-Linking"),(0,i.kt)("p",null,"Apiato offers a repository auto-linking feature that eliminates the need for manual linking of repositories & models.\nThis automatic linking process relies on adhering to standard Apiato naming conventions for repositories."),(0,i.kt)("p",null,"By following the repository naming ",(0,i.kt)("a",{parentName:"p",href:"#rules"},"rules")," outlined above,\nyou allow Apiato to automatically link your repositories to their corresponding models."),(0,i.kt)("p",null,"To summarize:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Repositories must be stored within the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Data/Repositories")," directory."),(0,i.kt)("li",{parentName:"ul"},"The repository name should mirror the corresponding model's name while appending a ",(0,i.kt)("inlineCode",{parentName:"li"},"Repository")," suffix. For instance, a ",(0,i.kt)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,i.kt)("inlineCode",{parentName:"li"},"UserRepository")," repository class.")),(0,i.kt)("h2",{id:"pagination"},"Pagination"),(0,i.kt)("p",null,"Pagination is automatically applied when you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"paginate")," method with a model repository:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"{\n $this->userRepository->paginate();\n}\n")),(0,i.kt)("p",null,"To move between pages of results, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?page=200\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," parameter in any ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," request that lists records."),(0,i.kt)("p",null,"Whenever a request returns paginated results,\nyou'll find information about the pagination in the ",(0,i.kt)("strong",{parentName:"p"},"meta")," section of the response,\nwhich tells you things like the total number of records, the number on the current page, and more."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},'"data": [...],\n"meta": {\n "pagination": {\n "total": 2000,\n "count": 30,\n "per_page": 10,\n "current_page": 20,\n "total_pages": 200,\n "links": []\n }\n}\n')),(0,i.kt)("h3",{id:"limiting-results-per-page"},"Limiting Results Per Page"),(0,i.kt)("p",null,"You can control the number of results displayed on a single page using the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," parameter."),(0,i.kt)("p",null,"By default, the pagination limit is set to 10.\nIf you don't specify the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=")," parameter in your request, the response will contain 10 records per page."),(0,i.kt)("p",null,"You can adjust the default pagination limit in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"PAGINATION_LIMIT_DEFAULT=10\n")),(0,i.kt)("p",null,"For instance, if you want to receive 100 resources per page, add the ",(0,i.kt)("inlineCode",{parentName:"p"},"?limit=100")," query parameter to your request:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100\n")),(0,i.kt)("p",null,"This will return 100 resources within a single page of results.\nYou can also combine the ",(0,i.kt)("inlineCode",{parentName:"p"},"limit")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"page")," query parameters to access the next 100 resources:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=100&page=2\n")),(0,i.kt)("h3",{id:"maximum-pagination-limit"},"Maximum Pagination Limit"),(0,i.kt)("p",null,"You can also set the maximum number of resources\nthat can be returned in a single page by setting the ",(0,i.kt)("inlineCode",{parentName:"p"},"$maxPaginationLimit")," property in your repository class."),(0,i.kt)("p",null,"For example, to set the maximum number of resources to 20, you can do the following:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $maxPaginationLimit = 20;\n")),(0,i.kt)("h3",{id:"disabling-pagination"},"Disabling Pagination"),(0,i.kt)("p",null,"You can allow clients to request all data that matches their criteria and disable pagination,\nwhich can be applied either project-wide or on a per-repository basis.\nThis enables a request to retrieve all matching data by specifying ",(0,i.kt)("inlineCode",{parentName:"p"},"limit=0"),"."),(0,i.kt)("p",null,"To retrieve all matching entities in a single page, you can use the following query parameter:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?limit=0\n")),(0,i.kt)("h4",{id:"project-wide"},"Project-Wide"),(0,i.kt)("p",null,"To allow disabling pagination for the entire project, set ",(0,i.kt)("inlineCode",{parentName:"p"},"PAGINATION_SKIP=true")," in the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("h4",{id:"per-repository"},"Per Repository"),(0,i.kt)("p",null,"If you wish to allow disabling pagination for a specific repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"$allowDisablePagination")," property in your repository class."),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Per-repository configurations take precedence and override the global settings.")),(0,i.kt)("h2",{id:"requestcriteria"},"RequestCriteria"),(0,i.kt)("p",null,"RequestCriteria is a standard Criteria implementation\nthat enables filters to be applied to the repository based on parameters sent in the request.\nIt allows for dynamic searches, data filtering, and query customization."),(0,i.kt)("p",null,"To utilize RequestCriteria, you need to apply it to the repository.\nApiato provides two methods, ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria"),", which are available on all repositories."),(0,i.kt)("p",null,"Here's an example of how to use RequestCriteria:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use App\\Containers\\AppSection\\User\\Data\\Repositories\\UserRepository;\nuse App\\Ship\\Parents\\Tasks\\Task as ParentTask;\n\nclass GetAllUsersTask extends ParentTask\n{\n public function __construct(\n protected readonly UserRepository $repository\n ) {\n }\n\n public function run(): mixed\n {\n // $this->repository->removeRequestCriteria();\n return $this->repository->addRequestCriteria()->paginate();\n }\n}\n")),(0,i.kt)("p",null,"It's important to note\nthat using the ",(0,i.kt)("inlineCode",{parentName:"p"},"removeRequestCriteria")," method is only relevant if you have previously applied ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria"),".\nIf it hasn't been applied, there is no need to remove it,\nas RequestCriteria is not automatically applied unless explicitly used."),(0,i.kt)("h3",{id:"sorting--ordering"},"Sorting & Ordering"),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameters to sort the data in the response."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter is typically employed in conjunction with the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter."),(0,i.kt)("p",null,"By default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"orderBy")," parameter sorts the data in ",(0,i.kt)("strong",{parentName:"p"},"Ascending")," order.\nTo sort the data in ",(0,i.kt)("strong",{parentName:"p"},"Descending")," order, you can include ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy=desc"),"."),(0,i.kt)("p",null,"For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=id&sortedBy=asc\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?orderBy=created_at&sortedBy=desc\n?orderBy=name&sortedBy=asc\n")),(0,i.kt)("p",null,"You can use the following values for the ",(0,i.kt)("inlineCode",{parentName:"p"},"sortedBy")," parameter:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"asc")," for Ascending."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"desc")," for Descending.")),(0,i.kt)("h3",{id:"searching"},"Searching"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"There is much more to searching than what is covered here.\nPlease refer to the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/andersao/l5-repository"},"L5 Repository documentation")," for more details.")),(0,i.kt)("p",null,"When the RequestCriteria is enabled for a specific route,\nyou can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in ",(0,i.kt)("inlineCode",{parentName:"p"},"GET")," HTTP requests on that route to perform searches."),(0,i.kt)("p",null,"To make the search feature work, you need to specify which fields from the model should be searchable.\nIn your repository,\nset the ",(0,i.kt)("inlineCode",{parentName:"p"},"fieldSearchable")," field with the names of the fields\nyou want to make searchable or specify a relation to the fields."),(0,i.kt)("p",null,"Here's an example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name',\n 'email',\n 'product.name'\n];\n")),(0,i.kt)("p",null,'You can also set the type of condition that will be used to perform the query. The default condition is "=":'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"protected $fieldSearchable = [\n 'name' => 'like',\n 'email', // Default Condition \"=\"\n 'your_field' => 'condition'\n];\n")),(0,i.kt)("p",null,"You can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"search")," parameter in various ways:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?search=John\n?search=name:John\n?search=name:John%20Doe\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Replace spaces with ",(0,i.kt)("inlineCode",{parentName:"p"},"%20")," in the URL (e.g., ",(0,i.kt)("inlineCode",{parentName:"p"},"search=keyword%20here"),").")),(0,i.kt)("h4",{id:"searching-with-hashed-ids"},"Searching with Hashed IDs"),(0,i.kt)("p",null,"If you have ",(0,i.kt)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," enabled and want to search a hashed field,\nlike a user ID, you need to instruct the ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestCriteria")," to decode it before performing the search."),(0,i.kt)("p",null,"For example,\nif you have a search query like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"`?search=id:XbPW7awNkzl83LD6;parent_id:aYOxlpzRMwrX3gD7;some_hashed_id:NxOpZowo9GmjKqdR`\n")),(0,i.kt)("p",null,"You should update your ",(0,i.kt)("inlineCode",{parentName:"p"},"addRequestCriteria")," method as follows:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->repository->addRequestCriteria(['id', 'parent_id', 'some_hashed_id'])->all();\n")),(0,i.kt)("h3",{id:"filtering"},"Filtering"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter can be used in any HTTP request to control the response size by specifying the data you want in the response."),(0,i.kt)("p",null,"For example, to retrieve only the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"status")," fields from a Model, you can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"filter")," parameter like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?filter=id;status\n")),(0,i.kt)("p",null,"The response will include only the specified fields, as shown in the example response:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved"\n }\n ]\n}\n')),(0,i.kt)("p",null,"It's important to note that the transformer used to format the data is also filtered.\nThis means that only the fields specified in the filter are present, and all other fields are excluded.\nThis filtering also applies to all ",(0,i.kt)("a",{parentName:"p",href:"/docs/components/main-components/transformers#including-relationships"},"included relationships")," of the object."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "id": "0one37vjk49rp5ym",\n "status": "approved",\n "products": {\n "data": [\n {\n "id": "bmo7y84xpgeza06k",\n "status": "pending"\n },\n {\n "id": "o0wzxbg0q4k7jp9d",\n "status": "fulfilled"\n }\n ]\n },\n "recipients": {\n "data": [\n {\n "id": "r6lbekg8rv5ozyad"\n }\n ]\n },\n "store": {\n "data": {\n "id": "r6lbekg8rv5ozyad"\n }\n }\n }\n ]\n}\n')),(0,i.kt)("h2",{id:"caching"},"Caching"),(0,i.kt)("p",null,"L5 Repository supports caching of queries.\nThis feature is ",(0,i.kt)("inlineCode",{parentName:"p"},"disabled")," by default.\nYou can enable caching in the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/repository.php")," config or the ",(0,i.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"'cache' => [\n 'enabled' => true,\n],\n")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-dotenv"},"ELOQUENT_QUERY_CACHE=true\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"As per the L5 Repository documentation,\nyou need to implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepository")," interface\nand use the ",(0,i.kt)("inlineCode",{parentName:"p"},"CacheableRepositoryTrait")," trait in your repository class to enable caching.\nHowever, Apiato already implements these two requirements in the parent ",(0,i.kt)("inlineCode",{parentName:"p"},"Repository")," class,")),(0,i.kt)("h3",{id:"skipping-cache"},"Skipping Cache"),(0,i.kt)("p",null,"You can skip the cache for a specific request by adding the ",(0,i.kt)("inlineCode",{parentName:"p"},"?skipCache=true")," query parameter to the request URL."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"?skipCache=true\n")),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"It's not recommended to skip the cache, but it's useful for testing purposes.")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9ba65a7f.92419976.js b/assets/js/9ba65a7f.92419976.js new file mode 100644 index 000000000..b3acaa035 --- /dev/null +++ b/assets/js/9ba65a7f.92419976.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8963],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>m});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,s=e.originalType,l=e.parentName,d=r(e,["components","mdxType","originalType","parentName"]),h=p(n),c=i,m=h["".concat(l,".").concat(c)]||h[c]||u[c]||s;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,o=new Array(s);o[0]=c;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[h]="string"==typeof e?e:i,o[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const s={title:"Tests",tags:["component","optional-component","test"]},o=void 0,r={unversionedId:"components/optional-components/tests",id:"components/optional-components/tests",title:"Tests",description:"Apiato is built with testing in mind.",source:"@site/docs/components/optional-components/tests.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/tests",permalink:"/docs/next/components/optional-components/tests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/tests.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"test",permalink:"/docs/next/tags/test"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",frontMatter:{title:"Tests",tags:["component","optional-component","test"]},sidebar:"tutorialSidebar",previous:{title:"Service Providers",permalink:"/docs/next/components/optional-components/service-providers"},next:{title:"Values",permalink:"/docs/next/components/optional-components/values"}},l={},p=[{value:"Definitions",id:"definitions",level:2},{value:"Unit tests",id:"unit-tests",level:4},{value:"Functional tests",id:"functional-tests",level:4},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Writing Tests",id:"writing-tests",level:2},{value:"Functional Test Helpers",id:"functional-test-helpers",level:2},{value:"Properties",id:"properties",level:3},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"access",id:"access",level:4},{value:"Methods",id:"methods",level:3},{value:"makeCall",id:"makecall",level:4},{value:"injectId",id:"injectid",level:4},{value:"getTestingUser",id:"gettestinguser",level:4},{value:"getTestingUserWithoutAccess",id:"gettestinguserwithoutaccess",level:4},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"Available Assertions",id:"available-assertions",level:2},{value:"assertModelCastsIsEmpty",id:"assertmodelcastsisempty",level:4},{value:"assertDatabaseTable",id:"assertdatabasetable",level:4},{value:"getGateMock",id:"getgatemock",level:4},{value:"inIds",id:"inids",level:4},{value:"Faker",id:"faker",level:2},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},h="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(h,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato is built with testing in mind.\nIn fact,\nsupport for testing with PHPUnit is included out of the box\nand a ",(0,i.kt)("inlineCode",{parentName:"p"},"phpunit.xml")," file is already set up for your application.\nIn addition to the testing capabilities provided by Laravel,\nApiato enhances the testing experience by including convenient helper methods.\nThese methods enable you to write expressive tests for your applications, further enhancing the testing process.\nYou can refer to Laravel documentation on ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.kt)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.kt)("h2",{id:"definitions"},"Definitions"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"unit-tests"},"Unit tests"),(0,i.kt)("p",null,"Unit tests are tests that focus on a very small, isolated portion of your code.\nIn fact, most unit tests probably focus on a single method."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"functional-tests"},"Functional tests"),(0,i.kt)("p",null,"Functional tests may test a larger portion of your code,\nincluding how several objects interact with each other or even a full HTTP request to a JSON endpoint.\nGenerally, most of your tests should be functional tests.\nThese types of tests provide the most confidence that your system as a whole is functioning as intended."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All container-specific Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All ",(0,i.kt)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All API Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All WEB Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All TestCases MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class. e.g.:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")),(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u251c\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTaskTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UnitTestCase.php\n\u2502 \u2514\u2500\u2500 UI\n\u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2514\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 ApiTestCase.php\n\u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 LoginTest.php\n\u2502 \u2502 \u251c\u2500\u2500 LogoutTest.php\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 WebTestCase.php\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Tests\n \u251c\u2500\u2500 Unit\n \u2502 \u251c\u2500\u2500 UrlRuleTest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 TestCase.php\n")),(0,i.kt)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.kt)("p",null,"Unit tests are defined in the same manner as you would define them in Laravel.\nHowever, Functional tests follow a distinct approach.\nHere's an example of how to write functional tests:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\UI\\API\\Tests\\Functional;\n\nuse App\\Containers\\AppSection\\User\\UI\\API\\Tests\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\n/**\n * @group user\n * @group api\n */\nclass FindUserByIdTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users/{id}';\n protected bool $auth = true;\n protected array $access = [\n 'permissions' => 'search-users',\n 'roles' => '',\n ];\n\n public function testFindUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertOk();\n $response->assertJson(\n static fn (AssertableJson $json) => $json->has('data')\n ->where('data.id', \\Hashids::encode($user->id))\n ->etc()\n );\n }\n}\n")),(0,i.kt)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.kt)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.kt)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.kt)("h3",{id:"properties"},"Properties"),(0,i.kt)("p",null,"Certain test helper methods access properties defined in your test class to execute their tasks effectively.\nBelow, we will explore these properties and their corresponding methods:"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.kt)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class FindUserByIdTest extends ApiTestCase\n{\n // highlight-start\n protected string $endpoint = 'get@v1/profile';\n // highlight-end\n \n public function testGetAuthenticatedUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->makeCall();\n // You can override the \"endpoint\" property in specific test methods\n // $response = $this->endpoint('get@v1/users')->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property is used to determine whether the endpoint being called requires authentication or not in your test class.\nIf you do not explicitly define the ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.kt)("p",null,"In the context of testing, when ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method will handle authentication by creating a testing user\n(if one is not already available) and injecting their access token into the headers before making the API call."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class ListUsersTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users';\n // highlight-start\n protected bool $auth = false;\n // highlight-end\n \n public function testListUsers(): void\n {\n $response = $this->makeCall();\n // You can override the \"auth\" property in specific test methods\n // $response = $this->auth(true)->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property is used\nto define roles or permissions that you want to assign to your testing users within a test class.\nWhen you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.kt)("p",null,"By setting the desired roles and permissions in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property,\nyou can conveniently configure the testing user with the necessary access rights for your test scenarios.\nThis ensures that the testing user has the appropriate privileges when interacting with the application during testing."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DeleteUserTest extends ApiTestCase\n{\n protected string $endpoint = 'delete@v1/users/{id}';\n // highlight-start\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'admin',\n ];\n // highlight-end\n \n public function testDeleteUser(): void\n {\n // The testing user will have the \"delete-users\" permission and \"admin\" role.\n // highlight-start\n $user = $this->getTestingUser();\n // highlight-end\n \n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertNoContent(); \n }\n}\n")),(0,i.kt)("h3",{id:"methods"},"Methods"),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUser"},"getTestingUser"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUserWithoutAccess"},"getTestingUserWithoutAccess"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#endpoint"},"endpoint"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#auth"},"auth")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"makecall"},"makeCall"),(0,i.kt)("p",null,"To make a request to your application, you may invoke the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.kt)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.kt)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#fluent-json-testing"},"helpful assertions"),"\nthat allow you to inspect your application's responses."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->makeCall();\n\n$this->makeCall([\n 'email' => $userDetails['email'],\n 'password' => $userDetails['password'],\n]);\n\n$this->makeCall($data, $headers);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"injectid"},"injectId"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// user with ID 100\n// endpoint = 'get@v1/users/{id}'\n\n$this->injectId($user->id)->makeCall();\n")),(0,i.kt)("p",null,"In this example, the original endpoint is ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.kt)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.kt)("p",null,"By default, ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint to replace with the provided id. Remember\nto provide the third parameter if your endpoint expects an id with a different name."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}/articles/{id}'\n// You can also chain multiple `injectId` calls!\n\n$this->injectId($articles->id)->injectId($user->id, replace: '{user_id}')->makeCall();\n")),(0,i.kt)("p",null,"When the ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method will automatically encode the provided ID before injecting it into the endpoint.\nHowever, you have the option to control this behavior by using the second parameter of the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.kt)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}'\n\n// this will encode the id automatically\n$this->injectId($user->id, skipEncoding: false, replace: '{user_id}')->makeCall($data);\n// this will skip the encoding\n$this->injectId($user->getHashedKey(), skipEncoding: true, replace: '{user_id}')->makeCall($data);\n")),(0,i.kt)("p",null,"By utilizing the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method, you can dynamically inject an ID into the endpoint,\nallowing you to test specific resources or scenarios that depend on resource identifiers."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.kt)("p",null,"When you call ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nit returns a testing user instance with randomly generated attributes and all the roles and permissions\nspecified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property.\nThis ensures that the testing user has the appropriate access rights for the defined roles and permissions.\nHowever,\nyou also have the flexibility\nto override these attributes and access rights by passing the desired values as arguments to the method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// The testing user will be created with randomly generated attributes \n// and will inherit the roles and permissions specified in the `$access` property.\n$user = $this->getTestingUser();\n\n// The testing user will be created with the provided attributes and access rights.\n$user = $this->getTestingUser([\n 'email' => 'hello@mail.test',\n 'name' => 'Hello',\n 'password' => 'secret',\n], [\n 'permissions' => 'jump',\n 'roles' => 'jumper',\n]);\n")),(0,i.kt)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method is configured to work with the default Apiato configuration.\nHowever, if you are using a custom user model,\nyou will need to update the ",(0,i.kt)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php"),".\nThis configuration file allows you\nto specify your custom user model and the corresponding model factory state for testing."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUserWithoutAccess")," method allows you to obtain a testing user instance that doesn't have any assigned permissions or roles.\nIt is a shortcut for ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.kt)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method allows you to specify the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default endpoint that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The order in which you call ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," method allows you\nto specify the authentication status of the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default authentication status that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.kt)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.kt)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.kt)("p",null,"Here's an example of how to use ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.kt)("p",null,"In the code snippet above, ",(0,i.kt)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.kt)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.kt)("p",null,"In this case, the assertion will ignore the ",(0,i.kt)("inlineCode",{parentName:"p"},"id"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.kt)("p",null,"By using the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"$casts")," property of a model does not contain any unexpected values,\nensuring that the model's attributes are not automatically casted."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.kt)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"expectedColumns")," array.\nThe array should be in the format ","['column_name' => 'column_type']",",\nwhere the column type is a string representing the expected data type of the column."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"getgatemock"},"getGateMock"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This assertion helps you to test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.kt)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method\nto determine whether a user has the necessary permissions to access a particular resource.\nThe primary objective is\nto test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// PUT '/users/{id}'\n\n// UpdateUserRequest.php\npublic function authorize(Gate $gate): bool\n{\n // Here, we check if the user's id sent in the request has the necessary permissions to 'update'.\n return $gate->allows('update', [User::find($this->id)]);\n}\n\n// UpdateUserRequestTest.php \npublic function testAuthorizeMethodGateCall(): void\n{\n $user = $this->getTestingUserWithoutAccess();\n $request = UpdateUserRequest::injectData([], $user)\n ->withUrlParameters(['id' => $user->id]);\n // If the id is sent as a body parameter in the request, you can use the following:\n // $request = UpdateUserRequest::injectData(['id' => $user->getHashedKey()], $ user);\n \n $gateMock = $this->getGateMock(policyMethodName: 'update', args: [\n // Ensure you obtain a fresh model instance; using the $user variable directly will cause the test to fail.\n User::find($user->id),\n ]);\n \n $this->assertTrue($request->authorize($gateMock));\n}\n")),(0,i.kt)("p",null,"In this code, we're examining the testing of the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method within a FormRequest class.\nThe main objective is to confirm that it appropriately interacts with Laravel's Gate functionality.\nThe test ensures that the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct parameters,\nchecking if users have the required permissions to perform updates.\nIf the authorization logic is correctly implemented, this test should pass,\nensuring that only users with the necessary permissions can perform updates."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"inids"},"inIds"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.kt)("p",null,"By leveraging the ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method, you can streamline your testing process when working with hashed identifiers,\nensuring that the expected hashed IDs are present within your model collections."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in the test file.\nInstead, it will be transformed into a helper function that you can utilize anywhere in your application.")),(0,i.kt)("h2",{id:"faker"},"Faker"),(0,i.kt)("p",null,"An instance of ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/FakerPHP/Faker"},"Faker")," is automatically provided in every test class, allowing you to generate fake data easily.\nYou can access it using ",(0,i.kt)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.kt)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.kt)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.kt)("p",null,"To test your application using live testing data,\nsuch as creating items in an inventory, you can utilize the feature designed specifically for this purpose.\nIt allows for the automatic generation of testing data,\nwhich can be helpful during staging or when real people are testing your application."),(0,i.kt)("p",null,"To create your live testing data, navigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Seeder/SeedTestingData.php")," seeder class.\nWithin this class, you can define the logic and data generation process for your testing data."),(0,i.kt)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.kt)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9ba65a7f.fc5bf3a5.js b/assets/js/9ba65a7f.fc5bf3a5.js deleted file mode 100644 index 70675372b..000000000 --- a/assets/js/9ba65a7f.fc5bf3a5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[8963],{3905:(e,t,n)=>{n.d(t,{Zo:()=>d,kt:()=>m});var a=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},h="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,s=e.originalType,l=e.parentName,d=r(e,["components","mdxType","originalType","parentName"]),h=p(n),c=i,m=h["".concat(l,".").concat(c)]||h[c]||u[c]||s;return n?a.createElement(m,o(o({ref:t},d),{},{components:n})):a.createElement(m,o({ref:t},d))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,o=new Array(s);o[0]=c;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[h]="string"==typeof e?e:i,o[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>s,metadata:()=>r,toc:()=>p});var a=n(87462),i=(n(67294),n(3905));const s={title:"Tests",tags:["component","optional-component","test"]},o=void 0,r={unversionedId:"components/optional-components/tests",id:"components/optional-components/tests",title:"Tests",description:"Apiato is built with testing in mind.",source:"@site/docs/components/optional-components/tests.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/tests",permalink:"/docs/next/components/optional-components/tests",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/tests.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"test",permalink:"/docs/next/tags/test"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Tests",tags:["component","optional-component","test"]},sidebar:"tutorialSidebar",previous:{title:"Service Providers",permalink:"/docs/next/components/optional-components/service-providers"},next:{title:"Values",permalink:"/docs/next/components/optional-components/values"}},l={},p=[{value:"Definitions",id:"definitions",level:2},{value:"Unit tests",id:"unit-tests",level:4},{value:"Functional tests",id:"functional-tests",level:4},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Writing Tests",id:"writing-tests",level:2},{value:"Functional Test Helpers",id:"functional-test-helpers",level:2},{value:"Properties",id:"properties",level:3},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"access",id:"access",level:4},{value:"Methods",id:"methods",level:3},{value:"makeCall",id:"makecall",level:4},{value:"injectId",id:"injectid",level:4},{value:"getTestingUser",id:"gettestinguser",level:4},{value:"getTestingUserWithoutAccess",id:"gettestinguserwithoutaccess",level:4},{value:"endpoint",id:"endpoint",level:4},{value:"auth",id:"auth",level:4},{value:"Available Assertions",id:"available-assertions",level:2},{value:"assertModelCastsIsEmpty",id:"assertmodelcastsisempty",level:4},{value:"assertDatabaseTable",id:"assertdatabasetable",level:4},{value:"getGateMock",id:"getgatemock",level:4},{value:"inIds",id:"inids",level:4},{value:"Faker",id:"faker",level:2},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},h="wrapper";function u(e){let{components:t,...n}=e;return(0,i.kt)(h,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Apiato is built with testing in mind.\nIn fact,\nsupport for testing with PHPUnit is included out of the box\nand a ",(0,i.kt)("inlineCode",{parentName:"p"},"phpunit.xml")," file is already set up for your application.\nIn addition to the testing capabilities provided by Laravel,\nApiato enhances the testing experience by including convenient helper methods.\nThese methods enable you to write expressive tests for your applications, further enhancing the testing process.\nYou can refer to Laravel documentation on ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.kt)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.kt)("h2",{id:"definitions"},"Definitions"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"unit-tests"},"Unit tests"),(0,i.kt)("p",null,"Unit tests are tests that focus on a very small, isolated portion of your code.\nIn fact, most unit tests probably focus on a single method."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"functional-tests"},"Functional tests"),(0,i.kt)("p",null,"Functional tests may test a larger portion of your code,\nincluding how several objects interact with each other or even a full HTTP request to a JSON endpoint.\nGenerally, most of your tests should be functional tests.\nThese types of tests provide the most confidence that your system as a whole is functioning as intended."),(0,i.kt)("h2",{id:"rules"},"Rules"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"All container-specific Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All ",(0,i.kt)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All API Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All WEB Functional tests:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/WEB/Tests/Functional")," directory."),(0,i.kt)("li",{parentName:"ul"},"MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")," class."))),(0,i.kt)("li",{parentName:"ul"},"All TestCases MUST extend the ",(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class. e.g.:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\TestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\API\\Tests\\ApiTestCase")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\UI\\WEB\\Tests\\WebTestCase")),(0,i.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.kt)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u251c\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTaskTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UnitTestCase.php\n\u2502 \u2514\u2500\u2500 UI\n\u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2514\u2500\u2500 Tests\n\u2502 \u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 DeleteUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 ApiTestCase.php\n\u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 LoginTest.php\n\u2502 \u2502 \u251c\u2500\u2500 LogoutTest.php\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 WebTestCase.php\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Tests\n \u251c\u2500\u2500 Unit\n \u2502 \u251c\u2500\u2500 UrlRuleTest.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 TestCase.php\n")),(0,i.kt)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.kt)("p",null,"Unit tests are defined in the same manner as you would define them in Laravel.\nHowever, Functional tests follow a distinct approach.\nHere's an example of how to write functional tests:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\UI\\API\\Tests\\Functional;\n\nuse App\\Containers\\AppSection\\User\\UI\\API\\Tests\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\n\n/**\n * @group user\n * @group api\n */\nclass FindUserByIdTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users/{id}';\n protected bool $auth = true;\n protected array $access = [\n 'permissions' => 'search-users',\n 'roles' => '',\n ];\n\n public function testFindUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertOk();\n $response->assertJson(\n static fn (AssertableJson $json) => $json->has('data')\n ->where('data.id', \\Hashids::encode($user->id))\n ->etc()\n );\n }\n}\n")),(0,i.kt)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.kt)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.kt)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.kt)("h3",{id:"properties"},"Properties"),(0,i.kt)("p",null,"Certain test helper methods access properties defined in your test class to execute their tasks effectively.\nBelow, we will explore these properties and their corresponding methods:"),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.kt)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class FindUserByIdTest extends ApiTestCase\n{\n // highlight-start\n protected string $endpoint = 'get@v1/profile';\n // highlight-end\n \n public function testGetAuthenticatedUser(): void\n {\n $user = $this->getTestingUser();\n\n $response = $this->makeCall();\n // You can override the \"endpoint\" property in specific test methods\n // $response = $this->endpoint('get@v1/users')->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property is used to determine whether the endpoint being called requires authentication or not in your test class.\nIf you do not explicitly define the ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property in your test class, it will be defaulted to ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.kt)("p",null,"In the context of testing, when ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method will handle authentication by creating a testing user\n(if one is not already available) and injecting their access token into the headers before making the API call."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class ListUsersTest extends ApiTestCase\n{\n protected string $endpoint = 'get@v1/users';\n // highlight-start\n protected bool $auth = false;\n // highlight-end\n \n public function testListUsers(): void\n {\n $response = $this->makeCall();\n // You can override the \"auth\" property in specific test methods\n // $response = $this->auth(true)->makeCall();\n \n $response->assertOk();\n // other assertions...\n }\n}\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"access"},"access"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property is used\nto define roles or permissions that you want to assign to your testing users within a test class.\nWhen you use the ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property."),(0,i.kt)("p",null,"By setting the desired roles and permissions in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property,\nyou can conveniently configure the testing user with the necessary access rights for your test scenarios.\nThis ensures that the testing user has the appropriate privileges when interacting with the application during testing."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"class DeleteUserTest extends ApiTestCase\n{\n protected string $endpoint = 'delete@v1/users/{id}';\n // highlight-start\n protected array $access = [\n 'permissions' => 'delete-users',\n 'roles' => 'admin',\n ];\n // highlight-end\n \n public function testDeleteUser(): void\n {\n // The testing user will have the \"delete-users\" permission and \"admin\" role.\n // highlight-start\n $user = $this->getTestingUser();\n // highlight-end\n \n $response = $this->injectId($user->id)->makeCall();\n\n $response->assertNoContent(); \n }\n}\n")),(0,i.kt)("h3",{id:"methods"},"Methods"),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUser"},"getTestingUser"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getTestingUserWithoutAccess"},"getTestingUserWithoutAccess"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#endpoint"},"endpoint"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#auth"},"auth")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"makecall"},"makeCall"),(0,i.kt)("p",null,"To make a request to your application, you may invoke the ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.kt)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.kt)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.kt)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#fluent-json-testing"},"helpful assertions"),"\nthat allow you to inspect your application's responses."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->makeCall();\n\n$this->makeCall([\n 'email' => $userDetails['email'],\n 'password' => $userDetails['password'],\n]);\n\n$this->makeCall($data, $headers);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"injectid"},"injectId"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// user with ID 100\n// endpoint = 'get@v1/users/{id}'\n\n$this->injectId($user->id)->makeCall();\n")),(0,i.kt)("p",null,"In this example, the original endpoint is ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.kt)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.kt)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.kt)("p",null,"By default, ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.kt)("inlineCode",{parentName:"p"},"{id}")," in the endpoint to replace with the provided id. Remember\nto provide the third parameter if your endpoint expects an id with a different name."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}/articles/{id}'\n// You can also chain multiple `injectId` calls!\n\n$this->injectId($articles->id)->injectId($user->id, replace: '{user_id}')->makeCall();\n")),(0,i.kt)("p",null,"When the ",(0,i.kt)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method will automatically encode the provided ID before injecting it into the endpoint.\nHowever, you have the option to control this behavior by using the second parameter of the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.kt)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// endpoint = 'get@v1/users/{user_id}'\n\n// this will encode the id automatically\n$this->injectId($user->id, skipEncoding: false, replace: '{user_id}')->makeCall($data);\n// this will skip the encoding\n$this->injectId($user->getHashedKey(), skipEncoding: true, replace: '{user_id}')->makeCall($data);\n")),(0,i.kt)("p",null,"By utilizing the ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method, you can dynamically inject an ID into the endpoint,\nallowing you to test specific resources or scenarios that depend on resource identifiers."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.kt)("p",null,"When you call ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nit returns a testing user instance with randomly generated attributes and all the roles and permissions\nspecified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property.\nThis ensures that the testing user has the appropriate access rights for the defined roles and permissions.\nHowever,\nyou also have the flexibility\nto override these attributes and access rights by passing the desired values as arguments to the method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// The testing user will be created with randomly generated attributes \n// and will inherit the roles and permissions specified in the `access` property.\n$user = $this->getTestingUser();\n\n// The testing user will be created with the provided attributes and access rights.\n$user = $this->getTestingUser([\n 'email' => 'hello@mail.test',\n 'name' => 'Hello',\n 'password' => 'secret',\n], [\n 'permissions' => 'jump',\n 'roles' => 'jumper',\n]);\n")),(0,i.kt)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.kt)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.kt)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser")," method is configured to work with the default Apiato configuration.\nHowever, if you are using a custom user model,\nyou will need to update the ",(0,i.kt)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/apiato.php"),".\nThis configuration file allows you\nto specify your custom user model and the corresponding model factory state for testing."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUserWithoutAccess")," method allows you to obtain a testing user instance that doesn't have any assigned permissions or roles.\nIt is a shortcut for ",(0,i.kt)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.kt)("inlineCode",{parentName:"p"},"access")," property."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"endpoint"},"endpoint"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method allows you to specify the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default endpoint that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The order in which you call ",(0,i.kt)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.kt)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"auth"},"auth"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," method allows you\nto specify the authentication status of the endpoint you want to test within your functional tests.\nThis method is especially useful\nwhen you need to override the default authentication status that is defined in the ",(0,i.kt)("inlineCode",{parentName:"p"},"auth")," property of the test class,\nspecifically for a particular test method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.kt)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.kt)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.kt)("p",null,(0,i.kt)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.kt)("br",{parentName:"p"}),"\n",(0,i.kt)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model is empty.\nBy default, the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model includes the ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.kt)("p",null,"Here's an example of how to use ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.kt)("p",null,"In the code snippet above, ",(0,i.kt)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of the model is empty,\nignoring the default ",(0,i.kt)("inlineCode",{parentName:"p"},"id")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.kt)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.kt)("p",null,"In this case, the assertion will ignore the ",(0,i.kt)("inlineCode",{parentName:"p"},"id"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.kt)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.kt)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of the model."),(0,i.kt)("p",null,"By using the ",(0,i.kt)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.kt)("inlineCode",{parentName:"p"},"casts")," property of a model does not contain any unexpected values,\nensuring that the model's attributes are not automatically casted."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.kt)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.kt)("inlineCode",{parentName:"p"},"expectedColumns")," array.\nThe array should be in the format ","['column_name' => 'column_type']",",\nwhere the column type is a string representing the expected data type of the column."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"getgatemock"},"getGateMock"),(0,i.kt)("blockquote",null,(0,i.kt)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.kt)("p",null,"This assertion helps you to test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.kt)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method\nto determine whether a user has the necessary permissions to access a particular resource.\nThe primary objective is\nto test whether the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"// PUT '/users/{id}'\n\n// UpdateUserRequest.php\npublic function authorize(Gate $gate): bool\n{\n // Here, we check if the user's id sent in the request has the necessary permissions to 'update'.\n return $gate->allows('update', [User::find($this->id)]);\n}\n\n// UpdateUserRequestTest.php \npublic function testAuthorizeMethodGateCall(): void\n{\n $user = $this->getTestingUserWithoutAccess();\n $request = UpdateUserRequest::injectData([], $user)\n ->withUrlParameters(['id' => $user->id]);\n // If the id is sent as a body parameter in the request, you can use the following:\n // $request = UpdateUserRequest::injectData(['id' => $user->getHashedKey()], $ user);\n \n $gateMock = $this->getGateMock(policyMethodName: 'update', args: [\n // Ensure you obtain a fresh model instance; using the $user variable directly will cause the test to fail.\n User::find($user->id),\n ]);\n \n $this->assertTrue($request->authorize($gateMock));\n}\n")),(0,i.kt)("p",null,"In this code, we're examining the testing of the ",(0,i.kt)("inlineCode",{parentName:"p"},"authorize")," method within a FormRequest class.\nThe main objective is to confirm that it appropriately interacts with Laravel's Gate functionality.\nThe test ensures that the ",(0,i.kt)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct parameters,\nchecking if users have the required permissions to perform updates.\nIf the authorization logic is correctly implemented, this test should pass,\nensuring that only users with the necessary permissions can perform updates."),(0,i.kt)("hr",null),(0,i.kt)("h4",{id:"inids"},"inIds"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.kt)("p",null,"By leveraging the ",(0,i.kt)("inlineCode",{parentName:"p"},"inIds")," method, you can streamline your testing process when working with hashed identifiers,\nensuring that the expected hashed IDs are present within your model collections."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in the test file.\nInstead, it will be transformed into a helper function that you can utilize anywhere in your application.")),(0,i.kt)("h2",{id:"faker"},"Faker"),(0,i.kt)("p",null,"An instance of ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/FakerPHP/Faker"},"Faker")," is automatically provided in every test class, allowing you to generate fake data easily.\nYou can access it using ",(0,i.kt)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.kt)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.kt)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.kt)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.kt)("p",null,"To test your application using live testing data,\nsuch as creating items in an inventory, you can utilize the feature designed specifically for this purpose.\nIt allows for the automatic generation of testing data,\nwhich can be helpful during staging or when real people are testing your application."),(0,i.kt)("p",null,"To create your live testing data, navigate to the ",(0,i.kt)("inlineCode",{parentName:"p"},"app/Ship/Seeder/SeedTestingData.php")," seeder class.\nWithin this class, you can define the logic and data generation process for your testing data."),(0,i.kt)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.kt)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9f93909f.03b35774.js b/assets/js/9f93909f.03b35774.js deleted file mode 100644 index c54107142..000000000 --- a/assets/js/9f93909f.03b35774.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[6891],{3905:(e,r,i)=>{i.d(r,{Zo:()=>d,kt:()=>m});var n=i(67294);function t(e,r,i){return r in e?Object.defineProperty(e,r,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[r]=i,e}function a(e,r){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var r=1;r=0||(t[i]=e[i]);return t}(e,r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(t[i]=e[i])}return t}var s=n.createContext({}),l=function(e){var r=n.useContext(s),i=r;return e&&(i="function"==typeof e?e(r):o(o({},r),e)),i},d=function(e){var r=l(e.components);return n.createElement(s.Provider,{value:r},e.children)},v="mdxType",c={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},u=n.forwardRef((function(e,r){var i=e.components,t=e.mdxType,a=e.originalType,s=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),v=l(i),u=t,m=v["".concat(s,".").concat(u)]||v[u]||c[u]||a;return i?n.createElement(m,o(o({ref:r},d),{},{components:i})):n.createElement(m,o({ref:r},d))}));function m(e,r){var i=arguments,t=r&&r.mdxType;if("string"==typeof e||t){var a=i.length,o=new Array(a);o[0]=u;var p={};for(var s in r)hasOwnProperty.call(r,s)&&(p[s]=r[s]);p.originalType=e,p[v]="string"==typeof e?e:t,o[1]=p;for(var l=2;l{i.r(r),i.d(r,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var n=i(87462),t=(i(67294),i(3905));const a={title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},o=void 0,p={unversionedId:"components/optional-components/service-providers",id:"components/optional-components/service-providers",title:"Service Providers",description:"Apiato service providers are just Laravel Service Providers,",source:"@site/docs/components/optional-components/service-providers.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/service-providers",permalink:"/docs/next/components/optional-components/service-providers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/service-providers.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"service-provider",permalink:"/docs/next/tags/service-provider"},{label:"middleware",permalink:"/docs/next/tags/middleware"},{label:"event",permalink:"/docs/next/tags/event"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},sidebar:"tutorialSidebar",previous:{title:"Seeders",permalink:"/docs/next/components/optional-components/seeders"},next:{title:"Tests",permalink:"/docs/next/components/optional-components/tests"}},s={},l=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Main Service Provider:",id:"main-service-provider",level:4},{value:"Register Providers",id:"register-providers",level:2},{value:"Container Service Providers",id:"container-service-providers",level:3},{value:"Main Service Provider",id:"main-service-provider-1",level:4},{value:"Additional Service Providers",id:"additional-service-providers",level:4},{value:"General Service Providers",id:"general-service-providers",level:3},{value:"Third Party Service Providers",id:"third-party-service-providers",level:3},{value:"Laravel Service Providers",id:"laravel-service-providers",level:2},{value:"Service Providers Registration Flow",id:"service-providers-registration-flow",level:2}],d={toc:l},v="wrapper";function c(e){let{components:r,...i}=e;return(0,t.kt)(v,(0,n.Z)({},d,i,{components:r,mdxType:"MDXLayout"}),(0,t.kt)("p",null,"Apiato service providers are just ",(0,t.kt)("a",{parentName:"p",href:"https://laravel.com/docs/providers"},"Laravel Service Providers"),",\nand they function in the exact same way as Laravel service providers.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.kt)("p",null,"To generate new service providers\nyou may use the ",(0,t.kt)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.kt)("p",null,"There are two distinct types of service providers within a container:\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," and additional service providers.\nThe Main Service Provider serves as the central registration point for all custom service providers within the container.\nIt orchestrates the setup and integration of these custom providers,\nensuring the seamless functioning of your application's components."),(0,t.kt)("h2",{id:"rules"},"Rules"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},"You MUST NOT register any Service Provider in the ",(0,t.kt)("inlineCode",{parentName:"li"},"config/app.php")," (except Laravel default service providers)."),(0,t.kt)("li",{parentName:"ul"},"Each Container:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MAY have one or many Service Providers."),(0,t.kt)("li",{parentName:"ul"},"MUST have a ",(0,t.kt)("inlineCode",{parentName:"li"},"Main Service Provider")," -> ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be named ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))))))),(0,t.kt)("li",{parentName:"ul"},"All container-specific Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in their respective container's ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All general Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Ship/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Prviders\\ShipProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All non-Laravel or third-party package Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))),(0,t.kt)("li",{parentName:"ul"},"When using Laravel ",(0,t.kt)("a",{parentName:"li",href:"#laravel-service-providers"},"default service providers"),":",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"AuthServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"BroadcastServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"EventServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MiddlewareServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MiddlewareServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"RouteServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"Parent{ServiceProviderName}"),". For example: ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentAuthServiceProvider"),".")))),(0,t.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.kt)("p",null,"The highlighted sections showcase service provider registration points:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider.php")," acts as the central registration point for custom service providers specific to a container."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"ShipProvider.php")," acts as the central registration point for the Ship (general) service providers.")),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AuthServiceProvider.php\n\u2502 \u251c\u2500\u2500 BroadcastServiceProvider.php\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php\n // highlight-start\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php\n // highlight-end\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php\n\u2502 \u251c\u2500\u2500 RouteServiceProvider.php\n\u2502 \u251c\u2500\u2500 CustomServiceProvider.php\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 RouteServiceProvider.php\n // highlight-start\n \u251c\u2500\u2500 ShipProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("h2",{id:"code-example"},"Code Example"),(0,t.kt)("h4",{id:"main-service-provider"},"Main Service Provider:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n // This providers will be automatically registered\n public array $serviceProviders = [\n CustomServiceProvider::class,\n MiddlewareServiceProvider::class,\n PassportServiceProvider::class,\n // ...\n ];\n\n public array $aliases = [\n // ...\n ];\n}\n")),(0,t.kt)("h2",{id:"register-providers"},"Register Providers"),(0,t.kt)("p",null,"The registration process for a service provider varies depending on its intended scope within the application.\nDifferent places are designated for different levels of service provider usage."),(0,t.kt)("p",null,"In essence, the decision of where to register a service provider boils down to two key factors:\nthe scope of service provider usage and the logical location for its registration."),(0,t.kt)("h3",{id:"container-service-providers"},"Container Service Providers"),(0,t.kt)("h4",{id:"main-service-provider-1"},"Main Service Provider"),(0,t.kt)("p",null,"A container ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," will be automatically registered by Apiato\nso manual registration isn't necessary.\nIn turn,\nMain Service Providers will register all service providers listed in their ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," property."),(0,t.kt)("h4",{id:"additional-service-providers"},"Additional Service Providers"),(0,t.kt)("p",null,"To register a provider,\nadd the provider's class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("p",null,"You can also list aliases in the ",(0,t.kt)("inlineCode",{parentName:"p"},"aliases")," property of the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $aliases = [\n 'CustomAlias' => CustomFacade::class,\n 'AnotherCustomAlias' => AnotherCustomFacade::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"general-service-providers"},"General Service Providers"),(0,t.kt)("p",null,"General service providers must be registered in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Providers\\ShipProvider")," class.\nThis can be done by adding the provider class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"third-party-service-providers"},"Third Party Service Providers"),(0,t.kt)("p",null,"When dealing with third-party packages that require service provider registration in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),",\nyou should follow these guidelines:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Specific Container Usage"),": If the package is used within a particular container, register its service provider in that container ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Framework-wide Usage"),": If the package is generic and used throughout the entire application, you can register its service provider in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," class. However, avoid registering it directly in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),"."))),(0,t.kt)("h2",{id:"laravel-service-providers"},"Laravel Service Providers"),(0,t.kt)("p",null,"Apiato introduces a refined organization for Laravel service providers.\nBy default, Laravel standard service providers,\nlocated in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/providers")," directory,\nhave been restructured in Apiato to reside in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/Ship/Parents/Providers")," directory."),(0,t.kt)("p",null,"Here's the mapping of Laravel's default service providers to their new locations in Apiato:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AppServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider"),(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"Note: Laravel ",(0,t.kt)("inlineCode",{parentName:"li"},"AppServiceProvider")," is renamed to ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider")," in Apiato."))),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AuthServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\BroadcastServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\EventServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\RouteServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"))),(0,t.kt)("p",null,"You should not modify these providers directly.\nInstead, extend them within your container's ",(0,t.kt)("inlineCode",{parentName:"p"},"Providers")," directory.\nFor instance,\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\Authentication\\Providers\\AuthServiceProvider")," class extends ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("p",null,"Those providers are not auto registered by default,\nthus writing any code there will not be available, unless you extend them.\nOnce extended, the child Service Provider should be registered in its container ",(0,t.kt)("inlineCode",{parentName:"p"},"MainServiceProvider"),",\nwhich makes it available."),(0,t.kt)("admonition",{type:"note"},(0,t.kt)("p",{parentName:"admonition"},"Do note that the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider")," is a unique case.\nBecause it's required by Apiato, it is registered by the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," and is loaded automatically.")),(0,t.kt)("h2",{id:"service-providers-registration-flow"},"Service Providers Registration Flow"),(0,t.kt)("p",null,"If you want to understand the service provider registration process,\nhere is a breakdown of the registration flow."),(0,t.kt)("p",null,"Consider the following folder structure:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u251c\u2500\u2500 ContainerA\n\u2502 \u2502 \u2514\u2500\u2500 Providers\n\u2502 \u2502 \u251c\u2500\u2500 CustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 ContainerB\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AnotherCustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2524\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 CustomGeneralServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 RouteServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u251c\u2500\u2500 ShipProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("p",null,"The following diagram illustrates the registration flow of service providers in the above folder structure:"),(0,t.kt)("mermaid",{value:"graph TB\n subgraph ContainerB[Container B]\n BMP[MainServiceProvider]\n BEP[EventServiceProvider]\n subgraph BServiceProviders[Service Providers]\n AnotherCustomServiceProvider\n BEP\n MiddlewareServiceProvider\n end\n BMP\n end\n \n subgraph ContainerA[Container A]\n AMP[MainServiceProvider]\n subgraph AServiceProviders[Service Providers]\n CustomServiceProvider\n EventServiceProvider\n end\n AMP\n end\n \n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eAMP\n SPLoader-- loads--\x3eBMP\n end\n \n AMP --\x3e|loads| AServiceProviders\n AServiceProviders --\x3e|registered in| AMP\n BMP --\x3e|loads| BServiceProviders\n BServiceProviders --\x3e|registered in| BMP"}))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9f93909f.19d3aa40.js b/assets/js/9f93909f.19d3aa40.js new file mode 100644 index 000000000..252f4d775 --- /dev/null +++ b/assets/js/9f93909f.19d3aa40.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[6891],{3905:(e,r,i)=>{i.d(r,{Zo:()=>d,kt:()=>m});var n=i(67294);function t(e,r,i){return r in e?Object.defineProperty(e,r,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[r]=i,e}function a(e,r){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);r&&(n=n.filter((function(r){return Object.getOwnPropertyDescriptor(e,r).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var r=1;r=0||(t[i]=e[i]);return t}(e,r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(t[i]=e[i])}return t}var s=n.createContext({}),l=function(e){var r=n.useContext(s),i=r;return e&&(i="function"==typeof e?e(r):o(o({},r),e)),i},d=function(e){var r=l(e.components);return n.createElement(s.Provider,{value:r},e.children)},v="mdxType",c={inlineCode:"code",wrapper:function(e){var r=e.children;return n.createElement(n.Fragment,{},r)}},u=n.forwardRef((function(e,r){var i=e.components,t=e.mdxType,a=e.originalType,s=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),v=l(i),u=t,m=v["".concat(s,".").concat(u)]||v[u]||c[u]||a;return i?n.createElement(m,o(o({ref:r},d),{},{components:i})):n.createElement(m,o({ref:r},d))}));function m(e,r){var i=arguments,t=r&&r.mdxType;if("string"==typeof e||t){var a=i.length,o=new Array(a);o[0]=u;var p={};for(var s in r)hasOwnProperty.call(r,s)&&(p[s]=r[s]);p.originalType=e,p[v]="string"==typeof e?e:t,o[1]=p;for(var l=2;l{i.r(r),i.d(r,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var n=i(87462),t=(i(67294),i(3905));const a={title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},o=void 0,p={unversionedId:"components/optional-components/service-providers",id:"components/optional-components/service-providers",title:"Service Providers",description:"Apiato service providers are just Laravel Service Providers,",source:"@site/docs/components/optional-components/service-providers.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/service-providers",permalink:"/docs/next/components/optional-components/service-providers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/service-providers.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"service-provider",permalink:"/docs/next/tags/service-provider"},{label:"middleware",permalink:"/docs/next/tags/middleware"},{label:"event",permalink:"/docs/next/tags/event"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",frontMatter:{title:"Service Providers",tags:["component","optional-component","service-provider","middleware","event"]},sidebar:"tutorialSidebar",previous:{title:"Seeders",permalink:"/docs/next/components/optional-components/seeders"},next:{title:"Tests",permalink:"/docs/next/components/optional-components/tests"}},s={},l=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Main Service Provider:",id:"main-service-provider",level:4},{value:"Register Providers",id:"register-providers",level:2},{value:"Container Service Providers",id:"container-service-providers",level:3},{value:"Main Service Provider",id:"main-service-provider-1",level:4},{value:"Additional Service Providers",id:"additional-service-providers",level:4},{value:"General Service Providers",id:"general-service-providers",level:3},{value:"Third Party Service Providers",id:"third-party-service-providers",level:3},{value:"Laravel Service Providers",id:"laravel-service-providers",level:2},{value:"Service Providers Registration Flow",id:"service-providers-registration-flow",level:2}],d={toc:l},v="wrapper";function c(e){let{components:r,...i}=e;return(0,t.kt)(v,(0,n.Z)({},d,i,{components:r,mdxType:"MDXLayout"}),(0,t.kt)("p",null,"Apiato service providers are just ",(0,t.kt)("a",{parentName:"p",href:"https://laravel.com/docs/providers"},"Laravel Service Providers"),",\nand they function in the exact same way as Laravel service providers.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.kt)("p",null,"To generate new service providers\nyou may use the ",(0,t.kt)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.kt)("p",null,"There are two distinct types of service providers within a container:\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," and additional service providers.\nThe Main Service Provider serves as the central registration point for all custom service providers within the container.\nIt orchestrates the setup and integration of these custom providers,\nensuring the seamless functioning of your application's components."),(0,t.kt)("h2",{id:"rules"},"Rules"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},"You MUST NOT register any Service Provider in the ",(0,t.kt)("inlineCode",{parentName:"li"},"config/app.php")," (except Laravel default service providers)."),(0,t.kt)("li",{parentName:"ul"},"Each Container:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MAY have one or many Service Providers."),(0,t.kt)("li",{parentName:"ul"},"MUST have a ",(0,t.kt)("inlineCode",{parentName:"li"},"Main Service Provider")," -> ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be named ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class.",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))))))),(0,t.kt)("li",{parentName:"ul"},"All container-specific Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in their respective container's ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All general Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.kt)("inlineCode",{parentName:"li"},"app/Ship/Providers")," directory."),(0,t.kt)("li",{parentName:"ul"},"MUST be registered in the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Prviders\\ShipProvider")," class."))),(0,t.kt)("li",{parentName:"ul"},"All non-Laravel or third-party package Service Providers:",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"MUST extend the ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider")," class."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentMainServiceProvider"),"."))),(0,t.kt)("li",{parentName:"ul"},"When using Laravel ",(0,t.kt)("a",{parentName:"li",href:"#laravel-service-providers"},"default service providers"),":",(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"AuthServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"BroadcastServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"EventServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MiddlewareServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MiddlewareServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"RouteServiceProvider")," MUST extend ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"),"."),(0,t.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.kt)("inlineCode",{parentName:"li"},"Parent{ServiceProviderName}"),". For example: ",(0,t.kt)("inlineCode",{parentName:"li"},"ParentAuthServiceProvider"),".")))),(0,t.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.kt)("p",null,"The highlighted sections showcase service provider registration points:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider.php")," acts as the central registration point for custom service providers specific to a container."),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"ShipProvider.php")," acts as the central registration point for the Ship (general) service providers.")),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u2514\u2500\u2500 Container\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AuthServiceProvider.php\n\u2502 \u251c\u2500\u2500 BroadcastServiceProvider.php\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php\n // highlight-start\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php\n // highlight-end\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php\n\u2502 \u251c\u2500\u2500 RouteServiceProvider.php\n\u2502 \u251c\u2500\u2500 CustomServiceProvider.php\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 RouteServiceProvider.php\n // highlight-start\n \u251c\u2500\u2500 ShipProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("h2",{id:"code-example"},"Code Example"),(0,t.kt)("h4",{id:"main-service-provider"},"Main Service Provider:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n // This providers will be automatically registered\n public array $serviceProviders = [\n CustomServiceProvider::class,\n MiddlewareServiceProvider::class,\n PassportServiceProvider::class,\n // ...\n ];\n\n public array $aliases = [\n // ...\n ];\n}\n")),(0,t.kt)("h2",{id:"register-providers"},"Register Providers"),(0,t.kt)("p",null,"The registration process for a service provider varies depending on its intended scope within the application.\nDifferent places are designated for different levels of service provider usage."),(0,t.kt)("p",null,"In essence, the decision of where to register a service provider boils down to two key factors:\nthe scope of service provider usage and the logical location for its registration."),(0,t.kt)("h3",{id:"container-service-providers"},"Container Service Providers"),(0,t.kt)("h4",{id:"main-service-provider-1"},"Main Service Provider"),(0,t.kt)("p",null,"A container ",(0,t.kt)("inlineCode",{parentName:"p"},"Main Service Provider")," will be automatically registered by Apiato\nso manual registration isn't necessary.\nIn turn,\nMain Service Providers will register all service providers listed in their ",(0,t.kt)("inlineCode",{parentName:"p"},"$serviceProviders")," property."),(0,t.kt)("h4",{id:"additional-service-providers"},"Additional Service Providers"),(0,t.kt)("p",null,"To register a provider,\nadd the provider's class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("p",null,"You can also list aliases in the ",(0,t.kt)("inlineCode",{parentName:"p"},"$aliases")," property of the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $aliases = [\n 'CustomAlias' => CustomFacade::class,\n 'AnotherCustomAlias' => AnotherCustomFacade::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"general-service-providers"},"General Service Providers"),(0,t.kt)("p",null,"General service providers must be registered in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Providers\\ShipProvider")," class.\nThis can be done by adding the provider class name to the ",(0,t.kt)("inlineCode",{parentName:"p"},"serviceProviders")," array."),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-php"},"public array $serviceProviders = [\n CustomServiceProvider::class,\n AnotherCustomServiceProvider::class,\n EventsServiceProvider::class,\n // ...\n];\n")),(0,t.kt)("h3",{id:"third-party-service-providers"},"Third Party Service Providers"),(0,t.kt)("p",null,"When dealing with third-party packages that require service provider registration in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),",\nyou should follow these guidelines:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Specific Container Usage"),": If the package is used within a particular container, register its service provider in that container ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\MainServiceProvider")," class.")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("p",{parentName:"li"},(0,t.kt)("strong",{parentName:"p"},"Framework-wide Usage"),": If the package is generic and used throughout the entire application, you can register its service provider in the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," class. However, avoid registering it directly in ",(0,t.kt)("inlineCode",{parentName:"p"},"config/app.php"),"."))),(0,t.kt)("h2",{id:"laravel-service-providers"},"Laravel Service Providers"),(0,t.kt)("p",null,"Apiato introduces a refined organization for Laravel service providers.\nBy default, Laravel standard service providers,\nlocated in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/providers")," directory,\nhave been restructured in Apiato to reside in the ",(0,t.kt)("inlineCode",{parentName:"p"},"app/Ship/Parents/Providers")," directory."),(0,t.kt)("p",null,"Here's the mapping of Laravel's default service providers to their new locations in Apiato:"),(0,t.kt)("ul",null,(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AppServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\MainServiceProvider"),(0,t.kt)("ul",{parentName:"li"},(0,t.kt)("li",{parentName:"ul"},"Note: Laravel ",(0,t.kt)("inlineCode",{parentName:"li"},"AppServiceProvider")," is renamed to ",(0,t.kt)("inlineCode",{parentName:"li"},"MainServiceProvider")," in Apiato."))),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\AuthServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\BroadcastServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\BroadcastServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\EventServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\EventServiceProvider")),(0,t.kt)("li",{parentName:"ul"},(0,t.kt)("inlineCode",{parentName:"li"},"App\\Providers\\RouteServiceProvider")," \u2192 ",(0,t.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider"))),(0,t.kt)("p",null,"You should not modify these providers directly.\nInstead, extend them within your container's ",(0,t.kt)("inlineCode",{parentName:"p"},"Providers")," directory.\nFor instance,\nthe ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\Authentication\\Providers\\AuthServiceProvider")," class extends ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\AuthServiceProvider"),"."),(0,t.kt)("p",null,"Those providers are not auto registered by default,\nthus writing any code there will not be available, unless you extend them.\nOnce extended, the child Service Provider should be registered in its container ",(0,t.kt)("inlineCode",{parentName:"p"},"MainServiceProvider"),",\nwhich makes it available."),(0,t.kt)("admonition",{type:"note"},(0,t.kt)("p",{parentName:"admonition"},"Do note that the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Parents\\Providers\\RouteServiceProvider")," is a unique case.\nBecause it's required by Apiato, it is registered by the ",(0,t.kt)("inlineCode",{parentName:"p"},"App\\Ship\\Prviders\\ShipProvider")," and is loaded automatically.")),(0,t.kt)("h2",{id:"service-providers-registration-flow"},"Service Providers Registration Flow"),(0,t.kt)("p",null,"If you want to understand the service provider registration process,\nhere is a breakdown of the registration flow."),(0,t.kt)("p",null,"Consider the following folder structure:"),(0,t.kt)("pre",null,(0,t.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u251c\u2500\u2500 Containers\n\u2502 \u2514\u2500\u2500 Section\n\u2502 \u251c\u2500\u2500 ContainerA\n\u2502 \u2502 \u2514\u2500\u2500 Providers\n\u2502 \u2502 \u251c\u2500\u2500 CustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2514\u2500\u2500 ContainerB\n\u2502 \u2514\u2500\u2500 Providers\n\u2502 \u251c\u2500\u2500 AnotherCustomServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u251c\u2500\u2500 EventServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2524\n\u2502 \u251c\u2500\u2500 MiddlewareServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2502 \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 Ship\n \u2514\u2500\u2500 Providers\n \u251c\u2500\u2500 CustomGeneralServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 RouteServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u251c\u2500\u2500 ShipProvider.php \u25c4\u2500\u2500registered\u2500in\u2500\u25c4\u2500\u2518\n \u2514\u2500\u2500 ...\n")),(0,t.kt)("p",null,"The following diagram illustrates the registration flow of service providers in the above folder structure:"),(0,t.kt)("mermaid",{value:"graph TB\n subgraph ContainerB[Container B]\n BMP[MainServiceProvider]\n BEP[EventServiceProvider]\n subgraph BServiceProviders[Service Providers]\n AnotherCustomServiceProvider\n BEP\n MiddlewareServiceProvider\n end\n BMP\n end\n \n subgraph ContainerA[Container A]\n AMP[MainServiceProvider]\n subgraph AServiceProviders[Service Providers]\n CustomServiceProvider\n EventServiceProvider\n end\n AMP\n end\n \n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eAMP\n SPLoader-- loads--\x3eBMP\n end\n \n AMP --\x3e|loads| AServiceProviders\n AServiceProviders --\x3e|registered in| AMP\n BMP --\x3e|loads| BServiceProviders\n BServiceProviders --\x3e|registered in| BMP"}))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/abe60d8e.096e572b.js b/assets/js/abe60d8e.096e572b.js deleted file mode 100644 index f51c46867..000000000 --- a/assets/js/abe60d8e.096e572b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[9772],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(67294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function l(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),u=p(t),c=r,h=u["".concat(s,".").concat(c)]||u[c]||m[c]||i;return t?a.createElement(h,l(l({ref:n},d),{},{components:t})):a.createElement(h,l({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,l=new Array(i);l[0]=c;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o[u]="string"==typeof e?e:r,l[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(87462),r=(t(67294),t(3905));const i={sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},l=void 0,o={unversionedId:"components/main-components/transformers",id:"version-12.x/components/main-components/transformers",title:"Transformers",description:"Transformers,",source:"@site/versioned_docs/version-12.x/components/main-components/transformers.md",sourceDirName:"components/main-components",slug:"/components/main-components/transformers",permalink:"/docs/components/main-components/transformers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/main-components/transformers.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"main-component",permalink:"/docs/tags/main-component"},{label:"transformer",permalink:"/docs/tags/transformer"},{label:"controller",permalink:"/docs/tags/controller"},{label:"response",permalink:"/docs/tags/response"},{label:"model",permalink:"/docs/tags/model"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},sidebar:"tutorialSidebar",previous:{title:"Models",permalink:"/docs/components/main-components/models"},next:{title:"Views",permalink:"/docs/components/main-components/views"}},s={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Including Relationships",id:"including-relationships",level:2},{value:"Defining Relationships",id:"defining-relationships",level:3},{value:"Include Per API Consumer Request",id:"include-per-api-consumer-request",level:3},{value:"Include By Default",id:"include-by-default",level:3},{value:"Resource Key",id:"resource-key",level:2},{value:"Setting the Resource Key",id:"setting-the-resource-key",level:3},{value:"Via Model",id:"via-model",level:4},{value:"Via Controller",id:"via-controller",level:4},{value:"Getting the Resource Key",id:"getting-the-resource-key",level:3},{value:"Transformer Example",id:"transformer-example",level:4},{value:"Response Example",id:"response-example",level:4},{value:"Helper Methods",id:"helper-methods",level:2},{value:"ifAdmin",id:"ifadmin",level:3},{value:"nullableItem",id:"nullableitem",level:3},{value:"Response Payload",id:"response-payload",level:2}],d={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,r.kt)(u,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Transformers,\noften referred to as Response Transformers, serve a similar purpose to Views, but specifically for JSON responses.\nWhile Views are responsible for presenting data in HTML format,\nTransformers take data and format it into JSON representation."),(0,r.kt)("p",null,"For more detailed information about transformers and their usage,\nyou can refer to the ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"official documentation of Fractal"),",\nwhich is the underlying library used for handling transformations in Apiato."),(0,r.kt)("p",null,"To generate new transformers\nyou may use the ",(0,r.kt)("inlineCode",{parentName:"p"},"apiato:generate:transformer")," interactive command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"php artisan apiato:generate:transformer\n")),(0,r.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,r.kt)("p",null,"Read ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,r.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Transformers)")),"."),(0,r.kt)("h2",{id:"rules"},"Rules"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"All Transformers:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,r.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Transformers")," directory."),(0,r.kt)("li",{parentName:"ul"},"MUST extend the ",(0,r.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Transformers\\Transformer")," class.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,r.kt)("inlineCode",{parentName:"li"},"ParentTransformer"),"."))),(0,r.kt)("li",{parentName:"ul"},"MUST have a public ",(0,r.kt)("inlineCode",{parentName:"li"},"transform")," method returning an array.")))),(0,r.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u2514\u2500\u2500 API\n \u2514\u2500\u2500 Transformers\n \u251c\u2500\u2500 TransformerA.php\n \u251c\u2500\u2500 TransformerB.php\n \u2514\u2500\u2500 ...\n")),(0,r.kt)("h2",{id:"code-example"},"Code Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Transformers\\Transformer as ParentTransformer;\n\nclass UserTransformer extends ParentTransformer\n{\n protected $availableIncludes = [];\n\n protected $defaultIncludes = [];\n\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(),\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n}\n")),(0,r.kt)("h2",{id:"including-relationships"},"Including Relationships"),(0,r.kt)("p",null,"You can include model relationships for complex data structures using the ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," query parameter.\nThese relationships can be included in the response either ",(0,r.kt)("a",{parentName:"p",href:"#include-per-api-consumer-request"},"per API consumer request")," or\n",(0,r.kt)("a",{parentName:"p",href:"#include-by-default"},"by default"),".\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," parameter can be used on any endpoint\nthat has ",(0,r.kt)("a",{parentName:"p",href:"#defining-relationships"},"relationships defined")," in its transformer."),(0,r.kt)("h3",{id:"defining-relationships"},"Defining Relationships"),(0,r.kt)("p",null,"To define relationships in the transformer, follow these two steps:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Define the relationship method in the transformer."),(0,r.kt)("li",{parentName:"ol"},"Add the relationship to the ",(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," property.")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," can be included in the response per API consumer request."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," are included in the response by default.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Any relationships not defined in the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," properties will be ignored.")),(0,r.kt)("p",null,"The relationship method should be named ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," and return a Fractal ",(0,r.kt)("inlineCode",{parentName:"p"},"Item")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," method will be called automatically by the Transformer\nwhen the relationship is requested."),(0,r.kt)("p",null,"For example, let's assume we have a ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," model with a ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," would have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeRoles")," method that returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n")),(0,r.kt)("p",null,"Now,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship can be included in the response\nby adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," property."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n];\n\n// or\n\nprotected array $defaultIncludes = [\n 'roles',\n];\n")),(0,r.kt)("h3",{id:"include-per-api-consumer-request"},"Include Per API Consumer Request"),(0,r.kt)("p",null,"In cases where you have multiple relationships for a model, such as ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"Roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationships,\nyou can include specific relationships in the response based on the API consumer's request.\nTo enable this,\nyou add the desired relationships to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property in the transformer\nand create corresponding methods for each relationship in the transformer to specify how to include that data."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships added to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n 'avatar',\n];\n\npublic function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"To request the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data along with the ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," resource, you can pass the ",(0,r.kt)("inlineCode",{parentName:"p"},"include=roles")," query parameter with the request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles\n")),(0,r.kt)("p",null,"This will include the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data in the response:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "object": "User",\n "id": "0one37vjk49rp5ym",\n "roles": [\n {\n "object": "Role",\n "id": "bmo7y84xpgeza06k"\n },\n // ...\n ]\n },\n // ...\n ]\n}\n')),(0,r.kt)("p",null,"You can also include multiple relationships by separating them with a comma:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles,avatar\n")),(0,r.kt)("p",null,"This includes both the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships in the response."),(0,r.kt)("p",null,"Nested includes are also possible.\nIf, for instance, the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," model has a relationship with an ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," object,\nyou can request nested includes using dot notation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=avatar,avatar.image\n")),(0,r.kt)("p",null,"This includes the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationship with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," nested under it in the response."),(0,r.kt)("p",null,"It's important to note that for nested includes, the nested relationship must also be defined.\nIn this example,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"AvatarTransformer")," would need\nto have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeImage")," method defined and the ",(0,r.kt)("inlineCode",{parentName:"p"},"image")," relationship added to it's ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property."),(0,r.kt)("p",null,"By defining the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," and implementing the corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," methods,\nyou allow API consumers to specify which related data they want in the response,\nenhancing the flexibility and efficiency of your API."),(0,r.kt)("h3",{id:"include-by-default"},"Include By Default"),(0,r.kt)("p",null,"To automatically include a relationship in every response generated by the transformer,\nyou can define the relationship in the transformer's ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," property.\nThis means\nthat the specified relationship will be included by default without the need for API consumers to request it explicitly."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," where the ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship is defined as a default include:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $defaultIncludes = [\n 'avatar',\n];\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"By setting the default includes in this manner,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship will automatically be included in every response created by this transformer.\nThis can simplify responses and reduce the need for additional API requests for related data,\nultimately enhancing the efficiency and usability of your API."),(0,r.kt)("h2",{id:"resource-key"},"Resource Key"),(0,r.kt)("p",null,"The transformer allows appending a resource key to the transformed resource.\nThis is useful when you want to have a consistent response payload format for all your resources."),(0,r.kt)("h3",{id:"setting-the-resource-key"},"Setting the Resource Key"),(0,r.kt)("p",null,"You can set the resource key in your response payload in two ways:"),(0,r.kt)("h4",{id:"via-model"},"Via Model"),(0,r.kt)("p",null,"Specify the resource key on the respective model by setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class User extends ParentUserModel\n{\n protected $resourceKey = 'User';\n}\n")),(0,r.kt)("h4",{id:"via-controller"},"Via Controller"),(0,r.kt)("p",null,"Manually set the resource key using the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," parameter in the controller's ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$this->transform($model, ModelTransformer::class, resourceKey: 'User');\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It's important to note that setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," using the ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method will only impact the ",(0,r.kt)("inlineCode",{parentName:"p"},"top level")," resource key\nand will not affect the resource keys of ",(0,r.kt)("inlineCode",{parentName:"p"},"included")," resources.")),(0,r.kt)("h3",{id:"getting-the-resource-key"},"Getting the Resource Key"),(0,r.kt)("p",null,"Retrieve the resource key from the model by calling the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method."),(0,r.kt)("p",null,"If no ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," is defined on the model, the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method will return the short class name of the model.\nFor instance, if no resource key is defined for ",(0,r.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\User\\Models\\User::class"),",\nthe default resource key will be ",(0,r.kt)("inlineCode",{parentName:"p"},"User"),"."),(0,r.kt)("h4",{id:"transformer-example"},"Transformer Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class UserTransformer extends ParentTransformer\n{\n // ...\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(), // <-- here\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n // ...\n}\n")),(0,r.kt)("h4",{id:"response-example"},"Response Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User", // <-- ResourceKey\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,r.kt)("h3",{id:"ifadmin"},"ifAdmin"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"ifAdmin")," method is used\nto merge the normal client response with additional or modified results intended for admin users."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function transform(Model $model)\n{\n $response = [\n 'object' => $model->getResourceKey(),\n 'id' => $model->getHashedKey(),\n 'another_field' => $model->anotherField,\n ];\n\n return $this->ifAdmin([\n 'real_id' => $model->id,\n 'created_at' => $model->created_at,\n 'updated_at' => $model->updated_at,\n 'readable_created_at' => $model->created_at->diffForHumans(),\n 'readable_updated_at' => $model->updated_at->diffForHumans(),\n ], $response);\n}\n")),(0,r.kt)("h3",{id:"nullableitem"},"nullableItem"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method returns an item if the model has a specific relationship, otherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $this->nullableItem($model->relation, new RelationTransformer();\n}\n")),(0,r.kt)("p",null,"If ",(0,r.kt)("inlineCode",{parentName:"p"},"$model->relation")," is not null (meaning it has a related model),\nthe method returns an item formatted using the specified transformer.\nOtherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method is a shortcut for the following code:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $model->relation ? $this->item($model->relation, new RelationTransformer()) : $this->primitive(null)\n}\n")),(0,r.kt)("h2",{id:"response-payload"},"Response Payload"),(0,r.kt)("p",null,"You have the flexibility to define your own custom response payload or utilize one of the supported serializers.\nSerializer classes let you switch between various output formats with minimal effect on your Transformers."),(0,r.kt)("p",null,"Current ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/serializers/"},"supported serializers"),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"ArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"DataArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"JsonApiSerializer"))),(0,r.kt)("p",null,"To modify the default Fractal serializer,\naccess the ",(0,r.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/fractal.php")," configuration file\nand update the ",(0,r.kt)("inlineCode",{parentName:"p"},"default_serializer")," setting to your preferred serializer."),(0,r.kt)("p",null,"By default, Apiato uses ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer"),".\nThis serializer is not to everyone\u2019s tastes, because it adds a ",(0,r.kt)("inlineCode",{parentName:"p"},"data")," namespace to the output.\nA very basic response of the ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," will look like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User",\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," is handy because it allows space for ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," data\n(like pagination, or totals) in both Items and Collections."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [ ... ],\n "meta": {\n "include": [\n "xxx",\n "yyy"\n ],\n "custom": [],\n "pagination": {\n "total": 999,\n "count": 999,\n "per_page": 999,\n "current_page": 999,\n "total_pages": 999,\n "links": {\n "next": "http://api.apiato.test/v1/accounts?page=999"\n }\n }\n }\n}\n')),(0,r.kt)("admonition",{title:"Further Reading",type:"info"},(0,r.kt)("p",{parentName:"admonition"},"For more detailed information, please refer to ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"Fractal")," and ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/spatie/laravel-fractal"},"Laravel Fractal Wrapper")," documentations.")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/abe60d8e.f86bb3a6.js b/assets/js/abe60d8e.f86bb3a6.js new file mode 100644 index 000000000..a2fa85e67 --- /dev/null +++ b/assets/js/abe60d8e.f86bb3a6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[9772],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(67294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function l(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),u=p(t),c=r,h=u["".concat(s,".").concat(c)]||u[c]||m[c]||i;return t?a.createElement(h,l(l({ref:n},d),{},{components:t})):a.createElement(h,l({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,l=new Array(i);l[0]=c;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o[u]="string"==typeof e?e:r,l[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(87462),r=(t(67294),t(3905));const i={sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},l=void 0,o={unversionedId:"components/main-components/transformers",id:"version-12.x/components/main-components/transformers",title:"Transformers",description:"Transformers,",source:"@site/versioned_docs/version-12.x/components/main-components/transformers.md",sourceDirName:"components/main-components",slug:"/components/main-components/transformers",permalink:"/docs/components/main-components/transformers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/main-components/transformers.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"main-component",permalink:"/docs/tags/main-component"},{label:"transformer",permalink:"/docs/tags/transformer"},{label:"controller",permalink:"/docs/tags/controller"},{label:"response",permalink:"/docs/tags/response"},{label:"model",permalink:"/docs/tags/model"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},sidebar:"tutorialSidebar",previous:{title:"Models",permalink:"/docs/components/main-components/models"},next:{title:"Views",permalink:"/docs/components/main-components/views"}},s={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Including Relationships",id:"including-relationships",level:2},{value:"Defining Relationships",id:"defining-relationships",level:3},{value:"Include Per API Consumer Request",id:"include-per-api-consumer-request",level:3},{value:"Include By Default",id:"include-by-default",level:3},{value:"Resource Key",id:"resource-key",level:2},{value:"Setting the Resource Key",id:"setting-the-resource-key",level:3},{value:"Via Model",id:"via-model",level:4},{value:"Via Controller",id:"via-controller",level:4},{value:"Getting the Resource Key",id:"getting-the-resource-key",level:3},{value:"Transformer Example",id:"transformer-example",level:4},{value:"Response Example",id:"response-example",level:4},{value:"Helper Methods",id:"helper-methods",level:2},{value:"ifAdmin",id:"ifadmin",level:3},{value:"nullableItem",id:"nullableitem",level:3},{value:"Response Payload",id:"response-payload",level:2}],d={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,r.kt)(u,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Transformers,\noften referred to as Response Transformers, serve a similar purpose to Views, but specifically for JSON responses.\nWhile Views are responsible for presenting data in HTML format,\nTransformers take data and format it into JSON representation."),(0,r.kt)("p",null,"For more detailed information about transformers and their usage,\nyou can refer to the ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"official documentation of Fractal"),",\nwhich is the underlying library used for handling transformations in Apiato."),(0,r.kt)("p",null,"To generate new transformers\nyou may use the ",(0,r.kt)("inlineCode",{parentName:"p"},"apiato:generate:transformer")," interactive command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"php artisan apiato:generate:transformer\n")),(0,r.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,r.kt)("p",null,"Read ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,r.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Transformers)")),"."),(0,r.kt)("h2",{id:"rules"},"Rules"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"All Transformers:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,r.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Transformers")," directory."),(0,r.kt)("li",{parentName:"ul"},"MUST extend the ",(0,r.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Transformers\\Transformer")," class.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,r.kt)("inlineCode",{parentName:"li"},"ParentTransformer"),"."))),(0,r.kt)("li",{parentName:"ul"},"MUST have a public ",(0,r.kt)("inlineCode",{parentName:"li"},"transform")," method returning an array.")))),(0,r.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u2514\u2500\u2500 API\n \u2514\u2500\u2500 Transformers\n \u251c\u2500\u2500 TransformerA.php\n \u251c\u2500\u2500 TransformerB.php\n \u2514\u2500\u2500 ...\n")),(0,r.kt)("h2",{id:"code-example"},"Code Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Transformers\\Transformer as ParentTransformer;\n\nclass UserTransformer extends ParentTransformer\n{\n protected $availableIncludes = [];\n\n protected $defaultIncludes = [];\n\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(),\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n}\n")),(0,r.kt)("h2",{id:"including-relationships"},"Including Relationships"),(0,r.kt)("p",null,"You can include model relationships for complex data structures using the ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," query parameter.\nThese relationships can be included in the response either ",(0,r.kt)("a",{parentName:"p",href:"#include-per-api-consumer-request"},"per API consumer request")," or\n",(0,r.kt)("a",{parentName:"p",href:"#include-by-default"},"by default"),".\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," parameter can be used on any endpoint\nthat has ",(0,r.kt)("a",{parentName:"p",href:"#defining-relationships"},"relationships defined")," in its transformer."),(0,r.kt)("h3",{id:"defining-relationships"},"Defining Relationships"),(0,r.kt)("p",null,"To define relationships in the transformer, follow these two steps:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Define the relationship method in the transformer."),(0,r.kt)("li",{parentName:"ol"},"Add the relationship to the ",(0,r.kt)("inlineCode",{parentName:"li"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"li"},"$defaultIncludes")," property.")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," can be included in the response per API consumer request."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," are included in the response by default.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Any relationships not defined in the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," properties will be ignored.")),(0,r.kt)("p",null,"The relationship method should be named ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," and return a Fractal ",(0,r.kt)("inlineCode",{parentName:"p"},"Item")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," method will be called automatically by the Transformer\nwhen the relationship is requested."),(0,r.kt)("p",null,"For example, let's assume we have a ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," model with a ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," would have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeRoles")," method that returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n")),(0,r.kt)("p",null,"Now,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship can be included in the response\nby adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," property."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n];\n\n// or\n\nprotected array $defaultIncludes = [\n 'roles',\n];\n")),(0,r.kt)("h3",{id:"include-per-api-consumer-request"},"Include Per API Consumer Request"),(0,r.kt)("p",null,"In cases where you have multiple relationships for a model, such as ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"Roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationships,\nyou can include specific relationships in the response based on the API consumer's request.\nTo enable this,\nyou add the desired relationships to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property in the transformer\nand create corresponding methods for each relationship in the transformer to specify how to include that data."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships added to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n 'avatar',\n];\n\npublic function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"To request the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data along with the ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," resource, you can pass the ",(0,r.kt)("inlineCode",{parentName:"p"},"include=roles")," query parameter with the request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles\n")),(0,r.kt)("p",null,"This will include the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data in the response:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "object": "User",\n "id": "0one37vjk49rp5ym",\n "roles": [\n {\n "object": "Role",\n "id": "bmo7y84xpgeza06k"\n },\n // ...\n ]\n },\n // ...\n ]\n}\n')),(0,r.kt)("p",null,"You can also include multiple relationships by separating them with a comma:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles,avatar\n")),(0,r.kt)("p",null,"This includes both the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships in the response."),(0,r.kt)("p",null,"Nested includes are also possible.\nIf, for instance, the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," model has a relationship with an ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," object,\nyou can request nested includes using dot notation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=avatar,avatar.image\n")),(0,r.kt)("p",null,"This includes the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationship with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," nested under it in the response."),(0,r.kt)("p",null,"It's important to note that for nested includes, the nested relationship must also be defined.\nIn this example,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"AvatarTransformer")," would need\nto have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeImage")," method defined and the ",(0,r.kt)("inlineCode",{parentName:"p"},"image")," relationship added to it's ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property."),(0,r.kt)("p",null,"By defining the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," and implementing the corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," methods,\nyou allow API consumers to specify which related data they want in the response,\nenhancing the flexibility and efficiency of your API."),(0,r.kt)("h3",{id:"include-by-default"},"Include By Default"),(0,r.kt)("p",null,"To automatically include a relationship in every response generated by the transformer,\nyou can define the relationship in the transformer's ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," property.\nThis means\nthat the specified relationship will be included by default without the need for API consumers to request it explicitly."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," where the ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship is defined as a default include:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $defaultIncludes = [\n 'avatar',\n];\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"By setting the default includes in this manner,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship will automatically be included in every response created by this transformer.\nThis can simplify responses and reduce the need for additional API requests for related data,\nultimately enhancing the efficiency and usability of your API."),(0,r.kt)("h2",{id:"resource-key"},"Resource Key"),(0,r.kt)("p",null,"The transformer allows appending a resource key to the transformed resource.\nThis is useful when you want to have a consistent response payload format for all your resources."),(0,r.kt)("h3",{id:"setting-the-resource-key"},"Setting the Resource Key"),(0,r.kt)("p",null,"You can set the resource key in your response payload in two ways:"),(0,r.kt)("h4",{id:"via-model"},"Via Model"),(0,r.kt)("p",null,"Specify the resource key on the respective model by setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"$resourceKey")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class User extends ParentUserModel\n{\n protected $resourceKey = 'User';\n}\n")),(0,r.kt)("h4",{id:"via-controller"},"Via Controller"),(0,r.kt)("p",null,"Manually set the resource key using the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," parameter in the controller's ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$this->transform($model, ModelTransformer::class, resourceKey: 'User');\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It's important to note that setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," using the ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method will only impact the ",(0,r.kt)("inlineCode",{parentName:"p"},"top level")," resource key\nand will not affect the resource keys of ",(0,r.kt)("inlineCode",{parentName:"p"},"included")," resources.")),(0,r.kt)("h3",{id:"getting-the-resource-key"},"Getting the Resource Key"),(0,r.kt)("p",null,"Retrieve the resource key from the model by calling the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method."),(0,r.kt)("p",null,"If no ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," is defined on the model, the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method will return the short class name of the model.\nFor instance, if no resource key is defined for ",(0,r.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\User\\Models\\User::class"),",\nthe default resource key will be ",(0,r.kt)("inlineCode",{parentName:"p"},"User"),"."),(0,r.kt)("h4",{id:"transformer-example"},"Transformer Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class UserTransformer extends ParentTransformer\n{\n // ...\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(), // <-- here\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n // ...\n}\n")),(0,r.kt)("h4",{id:"response-example"},"Response Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User", // <-- ResourceKey\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,r.kt)("h3",{id:"ifadmin"},"ifAdmin"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"ifAdmin")," method is used\nto merge the normal client response with additional or modified results intended for admin users."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function transform(Model $model)\n{\n $response = [\n 'object' => $model->getResourceKey(),\n 'id' => $model->getHashedKey(),\n 'another_field' => $model->anotherField,\n ];\n\n return $this->ifAdmin([\n 'real_id' => $model->id,\n 'created_at' => $model->created_at,\n 'updated_at' => $model->updated_at,\n 'readable_created_at' => $model->created_at->diffForHumans(),\n 'readable_updated_at' => $model->updated_at->diffForHumans(),\n ], $response);\n}\n")),(0,r.kt)("h3",{id:"nullableitem"},"nullableItem"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method returns an item if the model has a specific relationship, otherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $this->nullableItem($model->relation, new RelationTransformer();\n}\n")),(0,r.kt)("p",null,"If ",(0,r.kt)("inlineCode",{parentName:"p"},"$model->relation")," is not null (meaning it has a related model),\nthe method returns an item formatted using the specified transformer.\nOtherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method is a shortcut for the following code:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $model->relation ? $this->item($model->relation, new RelationTransformer()) : $this->primitive(null)\n}\n")),(0,r.kt)("h2",{id:"response-payload"},"Response Payload"),(0,r.kt)("p",null,"You have the flexibility to define your own custom response payload or utilize one of the supported serializers.\nSerializer classes let you switch between various output formats with minimal effect on your Transformers."),(0,r.kt)("p",null,"Current ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/serializers/"},"supported serializers"),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"ArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"DataArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"JsonApiSerializer"))),(0,r.kt)("p",null,"To modify the default Fractal serializer,\naccess the ",(0,r.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/fractal.php")," configuration file\nand update the ",(0,r.kt)("inlineCode",{parentName:"p"},"default_serializer")," setting to your preferred serializer."),(0,r.kt)("p",null,"By default, Apiato uses ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer"),".\nThis serializer is not to everyone\u2019s tastes, because it adds a ",(0,r.kt)("inlineCode",{parentName:"p"},"data")," namespace to the output.\nA very basic response of the ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," will look like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User",\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," is handy because it allows space for ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," data\n(like pagination, or totals) in both Items and Collections."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [ ... ],\n "meta": {\n "include": [\n "xxx",\n "yyy"\n ],\n "custom": [],\n "pagination": {\n "total": 999,\n "count": 999,\n "per_page": 999,\n "current_page": 999,\n "total_pages": 999,\n "links": {\n "next": "http://api.apiato.test/v1/accounts?page=999"\n }\n }\n }\n}\n')),(0,r.kt)("admonition",{title:"Further Reading",type:"info"},(0,r.kt)("p",{parentName:"admonition"},"For more detailed information, please refer to ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"Fractal")," and ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/spatie/laravel-fractal"},"Laravel Fractal Wrapper")," documentations.")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ad753fe5.65ee27eb.js b/assets/js/ad753fe5.65ee27eb.js deleted file mode 100644 index bd3f96b85..000000000 --- a/assets/js/ad753fe5.65ee27eb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2721],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),p=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=a,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||o;return n?r.createElement(h,i(i({ref:t},u),{},{components:n})):r.createElement(h,i({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:a,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=n(87462),a=(n(67294),n(3905));const o={sidebar_position:7,title:"Hash ID"},i=void 0,s={unversionedId:"security/hash-id",id:"security/hash-id",title:"Hash ID",description:"Hashing your internal ID's is a very helpful feature for many security reasons,",source:"@site/docs/security/hash-id.md",sourceDirName:"security",slug:"/security/hash-id",permalink:"/docs/next/security/hash-id",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/security/hash-id.md",tags:[],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Hash ID"},sidebar:"tutorialSidebar",previous:{title:"Password Reset",permalink:"/docs/next/security/password-reset"},next:{title:"Overview",permalink:"/docs/next/pacakges/"}},l={},p=[{value:"Enabling Hash ID",id:"enabling-hash-id",level:2},{value:"Usage",id:"usage",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Route Model Binding",id:"route-model-binding",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Hashing your internal ID's is a very helpful feature for many security reasons,\nsuch as preventing your internal ID's from being exposed to the public, your competitors, and hackers."),(0,a.kt)("h2",{id:"enabling-hash-id"},"Enabling Hash ID"),(0,a.kt)("p",null,"Set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID=true")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"BCMath PHP Extension is required to use this feature.")),(0,a.kt)("p",null,"Make sure to always use the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method on any model,\nwhenever you need to return an ID (mainly from ",(0,a.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/transformers"},"transformers"),")\nweather you are using Hash ID or not.\nIf Hash ID feature is disabled, the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method will return the normal ID."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"'id' => $user->getHashedKey(),\n")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"There are three ways to pass an ID to your system via the API:"),(0,a.kt)("p",null,"In URL:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items/XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"As query string:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items?id=XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"Or as HTTP request body:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,a.kt)("p",null,"Now you need to tell your API to ",(0,a.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/requests#request-properties"},"decode the ID")," for you.\nThis is done by setting the ",(0,a.kt)("inlineCode",{parentName:"p"},"decode")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"urlParameters")," properties on your Request class.\nAfter setting those properties,\nthe ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller."),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},(0,a.kt)("inlineCode",{parentName:"p"},"$request->id")," will return the decoded ID.")),(0,a.kt)("h2",{id:"configuration"},"Configuration"),(0,a.kt)("p",null,"Hash ID configuration is done in the ",(0,a.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/hashids.php")," config file.\nYou can set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file to any random string.\nApiato defaults to the ",(0,a.kt)("inlineCode",{parentName:"p"},"APP_KEY")," should this not be set."),(0,a.kt)("admonition",{type:"danger"},(0,a.kt)("p",{parentName:"admonition"},"The ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," acts as the salt during hashing of the ID. This should never be changed in production\nas it renders all previously generated IDs impossible to decode.")),(0,a.kt)("h2",{id:"route-model-binding"},"Route Model Binding"),(0,a.kt)("p",null,"Laravel ",(0,a.kt)("a",{parentName:"p",href:"https://laravel.com/docs/routing#route-model-binding"},"Route Model Binding")," feature is supported out of the box and Apiato will automatically decode the ID for you."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ad753fe5.c45a9b59.js b/assets/js/ad753fe5.c45a9b59.js new file mode 100644 index 000000000..5658bcf52 --- /dev/null +++ b/assets/js/ad753fe5.c45a9b59.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2721],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),p=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=a,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||o;return n?r.createElement(h,i(i({ref:t},u),{},{components:n})):r.createElement(h,i({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:a,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=n(87462),a=(n(67294),n(3905));const o={sidebar_position:7,title:"Hash ID"},i=void 0,s={unversionedId:"security/hash-id",id:"security/hash-id",title:"Hash ID",description:"Hashing your internal ID's is a very helpful feature for many security reasons,",source:"@site/docs/security/hash-id.md",sourceDirName:"security",slug:"/security/hash-id",permalink:"/docs/next/security/hash-id",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/security/hash-id.md",tags:[],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Hash ID"},sidebar:"tutorialSidebar",previous:{title:"Password Reset",permalink:"/docs/next/security/password-reset"},next:{title:"Overview",permalink:"/docs/next/pacakges/"}},l={},p=[{value:"Enabling Hash ID",id:"enabling-hash-id",level:2},{value:"Usage",id:"usage",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Route Model Binding",id:"route-model-binding",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Hashing your internal ID's is a very helpful feature for many security reasons,\nsuch as preventing your internal ID's from being exposed to the public, your competitors, and hackers."),(0,a.kt)("h2",{id:"enabling-hash-id"},"Enabling Hash ID"),(0,a.kt)("p",null,"Set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID=true")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"BCMath PHP Extension is required to use this feature.")),(0,a.kt)("p",null,"Make sure to always use the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method on any model,\nwhenever you need to return an ID (mainly from ",(0,a.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/transformers"},"transformers"),")\nweather you are using Hash ID or not.\nIf Hash ID feature is disabled, the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method will return the normal ID."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"'id' => $user->getHashedKey(),\n")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"There are three ways to pass an ID to your system via the API:"),(0,a.kt)("p",null,"In URL:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items/XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"As query string:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items?id=XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"Or as HTTP request body:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,a.kt)("p",null,"Now you need to tell your API to ",(0,a.kt)("a",{parentName:"p",href:"/docs/next/components/main-components/requests#request-properties"},"decode the ID")," for you.\nThis is done by setting the ",(0,a.kt)("inlineCode",{parentName:"p"},"$decode")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"$urlParameters")," properties on your Request class.\nAfter setting those properties,\nthe ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller."),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},(0,a.kt)("inlineCode",{parentName:"p"},"$request->id")," will return the decoded ID.")),(0,a.kt)("h2",{id:"configuration"},"Configuration"),(0,a.kt)("p",null,"Hash ID configuration is done in the ",(0,a.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/hashids.php")," config file.\nYou can set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file to any random string.\nApiato defaults to the ",(0,a.kt)("inlineCode",{parentName:"p"},"APP_KEY")," should this not be set."),(0,a.kt)("admonition",{type:"danger"},(0,a.kt)("p",{parentName:"admonition"},"The ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," acts as the salt during hashing of the ID. This should never be changed in production\nas it renders all previously generated IDs impossible to decode.")),(0,a.kt)("h2",{id:"route-model-binding"},"Route Model Binding"),(0,a.kt)("p",null,"Laravel ",(0,a.kt)("a",{parentName:"p",href:"https://laravel.com/docs/routing#route-model-binding"},"Route Model Binding")," feature is supported out of the box and Apiato will automatically decode the ID for you."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e63fc1b2.3fd4fcbd.js b/assets/js/e63fc1b2.3fd4fcbd.js deleted file mode 100644 index 0dcae7d84..000000000 --- a/assets/js/e63fc1b2.3fd4fcbd.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[5293],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(67294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function l(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),u=p(t),c=r,h=u["".concat(s,".").concat(c)]||u[c]||m[c]||i;return t?a.createElement(h,l(l({ref:n},d),{},{components:t})):a.createElement(h,l({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,l=new Array(i);l[0]=c;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o[u]="string"==typeof e?e:r,l[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(87462),r=(t(67294),t(3905));const i={sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},l=void 0,o={unversionedId:"components/main-components/transformers",id:"components/main-components/transformers",title:"Transformers",description:"Transformers,",source:"@site/docs/components/main-components/transformers.md",sourceDirName:"components/main-components",slug:"/components/main-components/transformers",permalink:"/docs/next/components/main-components/transformers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/main-components/transformers.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"main-component",permalink:"/docs/next/tags/main-component"},{label:"transformer",permalink:"/docs/next/tags/transformer"},{label:"controller",permalink:"/docs/next/tags/controller"},{label:"response",permalink:"/docs/next/tags/response"},{label:"model",permalink:"/docs/next/tags/model"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},sidebar:"tutorialSidebar",previous:{title:"Models",permalink:"/docs/next/components/main-components/models"},next:{title:"Views",permalink:"/docs/next/components/main-components/views"}},s={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Including Relationships",id:"including-relationships",level:2},{value:"Defining Relationships",id:"defining-relationships",level:3},{value:"Include Per API Consumer Request",id:"include-per-api-consumer-request",level:3},{value:"Include By Default",id:"include-by-default",level:3},{value:"Resource Key",id:"resource-key",level:2},{value:"Setting the Resource Key",id:"setting-the-resource-key",level:3},{value:"Via Model",id:"via-model",level:4},{value:"Via Controller",id:"via-controller",level:4},{value:"Getting the Resource Key",id:"getting-the-resource-key",level:3},{value:"Transformer Example",id:"transformer-example",level:4},{value:"Response Example",id:"response-example",level:4},{value:"Helper Methods",id:"helper-methods",level:2},{value:"ifAdmin",id:"ifadmin",level:3},{value:"nullableItem",id:"nullableitem",level:3},{value:"Response Payload",id:"response-payload",level:2}],d={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,r.kt)(u,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Transformers,\noften referred to as Response Transformers, serve a similar purpose to Views, but specifically for JSON responses.\nWhile Views are responsible for presenting data in HTML format,\nTransformers take data and format it into JSON representation."),(0,r.kt)("p",null,"For more detailed information about transformers and their usage,\nyou can refer to the ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"official documentation of Fractal"),",\nwhich is the underlying library used for handling transformations in Apiato."),(0,r.kt)("p",null,"To generate new transformers\nyou may use the ",(0,r.kt)("inlineCode",{parentName:"p"},"apiato:generate:transformer")," interactive command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"php artisan apiato:generate:transformer\n")),(0,r.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,r.kt)("p",null,"Read ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,r.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Transformers)")),"."),(0,r.kt)("h2",{id:"rules"},"Rules"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"All Transformers:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,r.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Transformers")," directory."),(0,r.kt)("li",{parentName:"ul"},"MUST extend the ",(0,r.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Transformers\\Transformer")," class.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,r.kt)("inlineCode",{parentName:"li"},"ParentTransformer"),"."))),(0,r.kt)("li",{parentName:"ul"},"MUST have a public ",(0,r.kt)("inlineCode",{parentName:"li"},"transform")," method returning an array.")))),(0,r.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u2514\u2500\u2500 API\n \u2514\u2500\u2500 Transformers\n \u251c\u2500\u2500 TransformerA.php\n \u251c\u2500\u2500 TransformerB.php\n \u2514\u2500\u2500 ...\n")),(0,r.kt)("h2",{id:"code-example"},"Code Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Transformers\\Transformer as ParentTransformer;\n\nclass UserTransformer extends ParentTransformer\n{\n protected $availableIncludes = [];\n\n protected $defaultIncludes = [];\n\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(),\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n}\n")),(0,r.kt)("h2",{id:"including-relationships"},"Including Relationships"),(0,r.kt)("p",null,"You can include model relationships for complex data structures using the ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," query parameter.\nThese relationships can be included in the response either ",(0,r.kt)("a",{parentName:"p",href:"#include-per-api-consumer-request"},"per API consumer request")," or\n",(0,r.kt)("a",{parentName:"p",href:"#include-by-default"},"by default"),".\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," parameter can be used on any endpoint\nthat has ",(0,r.kt)("a",{parentName:"p",href:"#defining-relationships"},"relationships defined")," in its transformer."),(0,r.kt)("h3",{id:"defining-relationships"},"Defining Relationships"),(0,r.kt)("p",null,"To define relationships in the transformer, follow these two steps:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Define the relationship method in the transformer."),(0,r.kt)("li",{parentName:"ol"},"Add the relationship to the ",(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," property.")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," can be included in the response per API consumer request."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," are included in the response by default.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Any relationships not defined in the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," properties will be ignored.")),(0,r.kt)("p",null,"The relationship method should be named ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," and return a Fractal ",(0,r.kt)("inlineCode",{parentName:"p"},"Item")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," method will be called automatically by the Transformer\nwhen the relationship is requested."),(0,r.kt)("p",null,"For example, let's assume we have a ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," model with a ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," would have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeRoles")," method that returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n")),(0,r.kt)("p",null,"Now,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship can be included in the response\nby adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," property."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n];\n\n// or\n\nprotected array $defaultIncludes = [\n 'roles',\n];\n")),(0,r.kt)("h3",{id:"include-per-api-consumer-request"},"Include Per API Consumer Request"),(0,r.kt)("p",null,"In cases where you have multiple relationships for a model, such as ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"Roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationships,\nyou can include specific relationships in the response based on the API consumer's request.\nTo enable this,\nyou add the desired relationships to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property in the transformer\nand create corresponding methods for each relationship in the transformer to specify how to include that data."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships added to the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n 'avatar',\n];\n\npublic function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"To request the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data along with the ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," resource, you can pass the ",(0,r.kt)("inlineCode",{parentName:"p"},"include=roles")," query parameter with the request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles\n")),(0,r.kt)("p",null,"This will include the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data in the response:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "object": "User",\n "id": "0one37vjk49rp5ym",\n "roles": [\n {\n "object": "Role",\n "id": "bmo7y84xpgeza06k"\n },\n // ...\n ]\n },\n // ...\n ]\n}\n')),(0,r.kt)("p",null,"You can also include multiple relationships by separating them with a comma:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles,avatar\n")),(0,r.kt)("p",null,"This includes both the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships in the response."),(0,r.kt)("p",null,"Nested includes are also possible.\nIf, for instance, the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," model has a relationship with an ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," object,\nyou can request nested includes using dot notation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=avatar,avatar.image\n")),(0,r.kt)("p",null,"This includes the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationship with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," nested under it in the response."),(0,r.kt)("p",null,"It's important to note that for nested includes, the nested relationship must also be defined.\nIn this example,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"AvatarTransformer")," would need\nto have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeImage")," method defined and the ",(0,r.kt)("inlineCode",{parentName:"p"},"image")," relationship added to it's ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," property."),(0,r.kt)("p",null,"By defining the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," and implementing the corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," methods,\nyou allow API consumers to specify which related data they want in the response,\nenhancing the flexibility and efficiency of your API."),(0,r.kt)("h3",{id:"include-by-default"},"Include By Default"),(0,r.kt)("p",null,"To automatically include a relationship in every response generated by the transformer,\nyou can define the relationship in the transformer's ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultIncludes")," property.\nThis means\nthat the specified relationship will be included by default without the need for API consumers to request it explicitly."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," where the ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship is defined as a default include:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $defaultIncludes = [\n 'avatar',\n];\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"By setting the default includes in this manner,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship will automatically be included in every response created by this transformer.\nThis can simplify responses and reduce the need for additional API requests for related data,\nultimately enhancing the efficiency and usability of your API."),(0,r.kt)("h2",{id:"resource-key"},"Resource Key"),(0,r.kt)("p",null,"The transformer allows appending a resource key to the transformed resource.\nThis is useful when you want to have a consistent response payload format for all your resources."),(0,r.kt)("h3",{id:"setting-the-resource-key"},"Setting the Resource Key"),(0,r.kt)("p",null,"You can set the resource key in your response payload in two ways:"),(0,r.kt)("h4",{id:"via-model"},"Via Model"),(0,r.kt)("p",null,"Specify the resource key on the respective model by setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class User extends ParentUserModel\n{\n protected $resourceKey = 'User';\n}\n")),(0,r.kt)("h4",{id:"via-controller"},"Via Controller"),(0,r.kt)("p",null,"Manually set the resource key using the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," parameter in the controller's ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$this->transform($model, ModelTransformer::class, resourceKey: 'User');\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It's important to note that setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," using the ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method will only impact the ",(0,r.kt)("inlineCode",{parentName:"p"},"top level")," resource key\nand will not affect the resource keys of ",(0,r.kt)("inlineCode",{parentName:"p"},"included")," resources.")),(0,r.kt)("h3",{id:"getting-the-resource-key"},"Getting the Resource Key"),(0,r.kt)("p",null,"Retrieve the resource key from the model by calling the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method."),(0,r.kt)("p",null,"If no ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," is defined on the model, the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method will return the short class name of the model.\nFor instance, if no resource key is defined for ",(0,r.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\User\\Models\\User::class"),",\nthe default resource key will be ",(0,r.kt)("inlineCode",{parentName:"p"},"User"),"."),(0,r.kt)("h4",{id:"transformer-example"},"Transformer Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class UserTransformer extends ParentTransformer\n{\n // ...\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(), // <-- here\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n // ...\n}\n")),(0,r.kt)("h4",{id:"response-example"},"Response Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User", // <-- ResourceKey\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,r.kt)("h3",{id:"ifadmin"},"ifAdmin"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"ifAdmin")," method is used\nto merge the normal client response with additional or modified results intended for admin users."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function transform(Model $model)\n{\n $response = [\n 'object' => $model->getResourceKey(),\n 'id' => $model->getHashedKey(),\n 'another_field' => $model->anotherField,\n ];\n\n return $this->ifAdmin([\n 'real_id' => $model->id,\n 'created_at' => $model->created_at,\n 'updated_at' => $model->updated_at,\n 'readable_created_at' => $model->created_at->diffForHumans(),\n 'readable_updated_at' => $model->updated_at->diffForHumans(),\n ], $response);\n}\n")),(0,r.kt)("h3",{id:"nullableitem"},"nullableItem"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method returns an item if the model has a specific relationship, otherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $this->nullableItem($model->relation, new RelationTransformer();\n}\n")),(0,r.kt)("p",null,"If ",(0,r.kt)("inlineCode",{parentName:"p"},"$model->relation")," is not null (meaning it has a related model),\nthe method returns an item formatted using the specified transformer.\nOtherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method is a shortcut for the following code:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $model->relation ? $this->item($model->relation, new RelationTransformer()) : $this->primitive(null)\n}\n")),(0,r.kt)("h2",{id:"response-payload"},"Response Payload"),(0,r.kt)("p",null,"You have the flexibility to define your own custom response payload or utilize one of the supported serializers.\nSerializer classes let you switch between various output formats with minimal effect on your Transformers."),(0,r.kt)("p",null,"Current ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/serializers/"},"supported serializers"),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"ArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"DataArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"JsonApiSerializer"))),(0,r.kt)("p",null,"To modify the default Fractal serializer,\naccess the ",(0,r.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/fractal.php")," configuration file\nand update the ",(0,r.kt)("inlineCode",{parentName:"p"},"default_serializer")," setting to your preferred serializer."),(0,r.kt)("p",null,"By default, Apiato uses ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer"),".\nThis serializer is not to everyone\u2019s tastes, because it adds a ",(0,r.kt)("inlineCode",{parentName:"p"},"data")," namespace to the output.\nA very basic response of the ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," will look like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User",\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," is handy because it allows space for ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," data\n(like pagination, or totals) in both Items and Collections."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [ ... ],\n "meta": {\n "include": [\n "xxx",\n "yyy"\n ],\n "custom": [],\n "pagination": {\n "total": 999,\n "count": 999,\n "per_page": 999,\n "current_page": 999,\n "total_pages": 999,\n "links": {\n "next": "http://api.apiato.test/v1/accounts?page=999"\n }\n }\n }\n}\n')),(0,r.kt)("admonition",{title:"Further Reading",type:"info"},(0,r.kt)("p",{parentName:"admonition"},"For more detailed information, please refer to ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"Fractal")," and ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/spatie/laravel-fractal"},"Laravel Fractal Wrapper")," documentations.")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e63fc1b2.8a6b4d4f.js b/assets/js/e63fc1b2.8a6b4d4f.js new file mode 100644 index 000000000..4e3c49ce9 --- /dev/null +++ b/assets/js/e63fc1b2.8a6b4d4f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[5293],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>h});var a=t(67294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function l(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,d=o(e,["components","mdxType","originalType","parentName"]),u=p(t),c=r,h=u["".concat(s,".").concat(c)]||u[c]||m[c]||i;return t?a.createElement(h,l(l({ref:n},d),{},{components:t})):a.createElement(h,l({ref:n},d))}));function h(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,l=new Array(i);l[0]=c;var o={};for(var s in n)hasOwnProperty.call(n,s)&&(o[s]=n[s]);o.originalType=e,o[u]="string"==typeof e?e:r,l[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(87462),r=(t(67294),t(3905));const i={sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},l=void 0,o={unversionedId:"components/main-components/transformers",id:"components/main-components/transformers",title:"Transformers",description:"Transformers,",source:"@site/docs/components/main-components/transformers.md",sourceDirName:"components/main-components",slug:"/components/main-components/transformers",permalink:"/docs/next/components/main-components/transformers",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/main-components/transformers.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"main-component",permalink:"/docs/next/tags/main-component"},{label:"transformer",permalink:"/docs/next/tags/transformer"},{label:"controller",permalink:"/docs/next/tags/controller"},{label:"response",permalink:"/docs/next/tags/response"},{label:"model",permalink:"/docs/next/tags/model"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Transformers",tags:["component","main-component","transformer","controller","response","model"]},sidebar:"tutorialSidebar",previous:{title:"Models",permalink:"/docs/next/components/main-components/models"},next:{title:"Views",permalink:"/docs/next/components/main-components/views"}},s={},p=[{value:"Definition & Principles",id:"definition--principles",level:2},{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Including Relationships",id:"including-relationships",level:2},{value:"Defining Relationships",id:"defining-relationships",level:3},{value:"Include Per API Consumer Request",id:"include-per-api-consumer-request",level:3},{value:"Include By Default",id:"include-by-default",level:3},{value:"Resource Key",id:"resource-key",level:2},{value:"Setting the Resource Key",id:"setting-the-resource-key",level:3},{value:"Via Model",id:"via-model",level:4},{value:"Via Controller",id:"via-controller",level:4},{value:"Getting the Resource Key",id:"getting-the-resource-key",level:3},{value:"Transformer Example",id:"transformer-example",level:4},{value:"Response Example",id:"response-example",level:4},{value:"Helper Methods",id:"helper-methods",level:2},{value:"ifAdmin",id:"ifadmin",level:3},{value:"nullableItem",id:"nullableitem",level:3},{value:"Response Payload",id:"response-payload",level:2}],d={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,r.kt)(u,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Transformers,\noften referred to as Response Transformers, serve a similar purpose to Views, but specifically for JSON responses.\nWhile Views are responsible for presenting data in HTML format,\nTransformers take data and format it into JSON representation."),(0,r.kt)("p",null,"For more detailed information about transformers and their usage,\nyou can refer to the ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"official documentation of Fractal"),",\nwhich is the underlying library used for handling transformations in Apiato."),(0,r.kt)("p",null,"To generate new transformers\nyou may use the ",(0,r.kt)("inlineCode",{parentName:"p"},"apiato:generate:transformer")," interactive command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"php artisan apiato:generate:transformer\n")),(0,r.kt)("h2",{id:"definition--principles"},"Definition & Principles"),(0,r.kt)("p",null,"Read ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/Mahmoudz/Porto#definitions--principles"},(0,r.kt)("strong",{parentName:"a"},"Porto SAP Documentation (#Transformers)")),"."),(0,r.kt)("h2",{id:"rules"},"Rules"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"All Transformers:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"MUST be placed in the ",(0,r.kt)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/UI/API/Transformers")," directory."),(0,r.kt)("li",{parentName:"ul"},"MUST extend the ",(0,r.kt)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Transformers\\Transformer")," class.",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,r.kt)("inlineCode",{parentName:"li"},"ParentTransformer"),"."))),(0,r.kt)("li",{parentName:"ul"},"MUST have a public ",(0,r.kt)("inlineCode",{parentName:"li"},"transform")," method returning an array.")))),(0,r.kt)("h2",{id:"folder-structure"},"Folder Structure"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-markdown"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u2514\u2500\u2500 UI\n \u2514\u2500\u2500 API\n \u2514\u2500\u2500 Transformers\n \u251c\u2500\u2500 TransformerA.php\n \u251c\u2500\u2500 TransformerB.php\n \u2514\u2500\u2500 ...\n")),(0,r.kt)("h2",{id:"code-example"},"Code Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Transformers\\Transformer as ParentTransformer;\n\nclass UserTransformer extends ParentTransformer\n{\n protected $availableIncludes = [];\n\n protected $defaultIncludes = [];\n\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(),\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n}\n")),(0,r.kt)("h2",{id:"including-relationships"},"Including Relationships"),(0,r.kt)("p",null,"You can include model relationships for complex data structures using the ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," query parameter.\nThese relationships can be included in the response either ",(0,r.kt)("a",{parentName:"p",href:"#include-per-api-consumer-request"},"per API consumer request")," or\n",(0,r.kt)("a",{parentName:"p",href:"#include-by-default"},"by default"),".\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include")," parameter can be used on any endpoint\nthat has ",(0,r.kt)("a",{parentName:"p",href:"#defining-relationships"},"relationships defined")," in its transformer."),(0,r.kt)("h3",{id:"defining-relationships"},"Defining Relationships"),(0,r.kt)("p",null,"To define relationships in the transformer, follow these two steps:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Define the relationship method in the transformer."),(0,r.kt)("li",{parentName:"ol"},"Add the relationship to the ",(0,r.kt)("inlineCode",{parentName:"li"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"li"},"$defaultIncludes")," property.")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"availableIncludes")," can be included in the response per API consumer request."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"defaultIncludes")," are included in the response by default.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Any relationships not defined in the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," properties will be ignored.")),(0,r.kt)("p",null,"The relationship method should be named ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," and return a Fractal ",(0,r.kt)("inlineCode",{parentName:"p"},"Item")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," method will be called automatically by the Transformer\nwhen the relationship is requested."),(0,r.kt)("p",null,"For example, let's assume we have a ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," model with a ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship.\nThe ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," would have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeRoles")," method that returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"Collection")," object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n")),(0,r.kt)("p",null,"Now,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," relationship can be included in the response\nby adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," property."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n];\n\n// or\n\nprotected array $defaultIncludes = [\n 'roles',\n];\n")),(0,r.kt)("h3",{id:"include-per-api-consumer-request"},"Include Per API Consumer Request"),(0,r.kt)("p",null,"In cases where you have multiple relationships for a model, such as ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"Roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationships,\nyou can include specific relationships in the response based on the API consumer's request.\nTo enable this,\nyou add the desired relationships to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property in the transformer\nand create corresponding methods for each relationship in the transformer to specify how to include that data."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," with ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships added to the ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $availableIncludes = [\n 'roles',\n 'avatar',\n];\n\npublic function includeRoles(User $user): Collection\n{\n return $this->collection($user->roles, new RoleTransformer());\n}\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"To request the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data along with the ",(0,r.kt)("inlineCode",{parentName:"p"},"User")," resource, you can pass the ",(0,r.kt)("inlineCode",{parentName:"p"},"include=roles")," query parameter with the request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles\n")),(0,r.kt)("p",null,"This will include the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," data in the response:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [\n {\n "object": "User",\n "id": "0one37vjk49rp5ym",\n "roles": [\n {\n "object": "Role",\n "id": "bmo7y84xpgeza06k"\n },\n // ...\n ]\n },\n // ...\n ]\n}\n')),(0,r.kt)("p",null,"You can also include multiple relationships by separating them with a comma:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=roles,avatar\n")),(0,r.kt)("p",null,"This includes both the ",(0,r.kt)("inlineCode",{parentName:"p"},"roles")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationships in the response."),(0,r.kt)("p",null,"Nested includes are also possible.\nIf, for instance, the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," model has a relationship with an ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," object,\nyou can request nested includes using dot notation:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"api.apiato.test/v1/users?include=avatar,avatar.image\n")),(0,r.kt)("p",null,"This includes the ",(0,r.kt)("inlineCode",{parentName:"p"},"Avatar")," relationship with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Image")," nested under it in the response."),(0,r.kt)("p",null,"It's important to note that for nested includes, the nested relationship must also be defined.\nIn this example,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"AvatarTransformer")," would need\nto have an ",(0,r.kt)("inlineCode",{parentName:"p"},"includeImage")," method defined and the ",(0,r.kt)("inlineCode",{parentName:"p"},"image")," relationship added to it's ",(0,r.kt)("inlineCode",{parentName:"p"},"$availableIncludes")," property."),(0,r.kt)("p",null,"By defining the ",(0,r.kt)("inlineCode",{parentName:"p"},"availableIncludes")," and implementing the corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"include{RelationshipName}")," methods,\nyou allow API consumers to specify which related data they want in the response,\nenhancing the flexibility and efficiency of your API."),(0,r.kt)("h3",{id:"include-by-default"},"Include By Default"),(0,r.kt)("p",null,"To automatically include a relationship in every response generated by the transformer,\nyou can define the relationship in the transformer's ",(0,r.kt)("inlineCode",{parentName:"p"},"$defaultIncludes")," property.\nThis means\nthat the specified relationship will be included by default without the need for API consumers to request it explicitly."),(0,r.kt)("p",null,"Here's an example using a ",(0,r.kt)("inlineCode",{parentName:"p"},"UserTransformer")," where the ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship is defined as a default include:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"protected array $defaultIncludes = [\n 'avatar',\n];\n\npublic function includeAvatar(User $user): Item\n{\n return $this->item($user->avatar, new AvatarTransformer());\n}\n")),(0,r.kt)("p",null,"By setting the default includes in this manner,\nthe ",(0,r.kt)("inlineCode",{parentName:"p"},"avatar")," relationship will automatically be included in every response created by this transformer.\nThis can simplify responses and reduce the need for additional API requests for related data,\nultimately enhancing the efficiency and usability of your API."),(0,r.kt)("h2",{id:"resource-key"},"Resource Key"),(0,r.kt)("p",null,"The transformer allows appending a resource key to the transformed resource.\nThis is useful when you want to have a consistent response payload format for all your resources."),(0,r.kt)("h3",{id:"setting-the-resource-key"},"Setting the Resource Key"),(0,r.kt)("p",null,"You can set the resource key in your response payload in two ways:"),(0,r.kt)("h4",{id:"via-model"},"Via Model"),(0,r.kt)("p",null,"Specify the resource key on the respective model by setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"$resourceKey")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class User extends ParentUserModel\n{\n protected $resourceKey = 'User';\n}\n")),(0,r.kt)("h4",{id:"via-controller"},"Via Controller"),(0,r.kt)("p",null,"Manually set the resource key using the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," parameter in the controller's ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$this->transform($model, ModelTransformer::class, resourceKey: 'User');\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It's important to note that setting the ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," using the ",(0,r.kt)("inlineCode",{parentName:"p"},"transform")," method will only impact the ",(0,r.kt)("inlineCode",{parentName:"p"},"top level")," resource key\nand will not affect the resource keys of ",(0,r.kt)("inlineCode",{parentName:"p"},"included")," resources.")),(0,r.kt)("h3",{id:"getting-the-resource-key"},"Getting the Resource Key"),(0,r.kt)("p",null,"Retrieve the resource key from the model by calling the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method."),(0,r.kt)("p",null,"If no ",(0,r.kt)("inlineCode",{parentName:"p"},"resourceKey")," is defined on the model, the ",(0,r.kt)("inlineCode",{parentName:"p"},"getResourceKey")," method will return the short class name of the model.\nFor instance, if no resource key is defined for ",(0,r.kt)("inlineCode",{parentName:"p"},"App\\Containers\\AppSection\\User\\Models\\User::class"),",\nthe default resource key will be ",(0,r.kt)("inlineCode",{parentName:"p"},"User"),"."),(0,r.kt)("h4",{id:"transformer-example"},"Transformer Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"class UserTransformer extends ParentTransformer\n{\n // ...\n public function transform(User $user)\n {\n return [\n 'object' => $user->getResourceKey(), // <-- here\n 'id' => $user->getHashedKey(),\n 'name' => $user->name,\n // ...\n ];\n }\n // ...\n}\n")),(0,r.kt)("h4",{id:"response-example"},"Response Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User", // <-- ResourceKey\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("h2",{id:"helper-methods"},"Helper Methods"),(0,r.kt)("h3",{id:"ifadmin"},"ifAdmin"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"ifAdmin")," method is used\nto merge the normal client response with additional or modified results intended for admin users."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"public function transform(Model $model)\n{\n $response = [\n 'object' => $model->getResourceKey(),\n 'id' => $model->getHashedKey(),\n 'another_field' => $model->anotherField,\n ];\n\n return $this->ifAdmin([\n 'real_id' => $model->id,\n 'created_at' => $model->created_at,\n 'updated_at' => $model->updated_at,\n 'readable_created_at' => $model->created_at->diffForHumans(),\n 'readable_updated_at' => $model->updated_at->diffForHumans(),\n ], $response);\n}\n")),(0,r.kt)("h3",{id:"nullableitem"},"nullableItem"),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method returns an item if the model has a specific relationship, otherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $this->nullableItem($model->relation, new RelationTransformer();\n}\n")),(0,r.kt)("p",null,"If ",(0,r.kt)("inlineCode",{parentName:"p"},"$model->relation")," is not null (meaning it has a related model),\nthe method returns an item formatted using the specified transformer.\nOtherwise, it returns ",(0,r.kt)("inlineCode",{parentName:"p"},"null"),"."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"nullableItem")," method is a shortcut for the following code:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\Resource\\Primitive;\n\npublic function includeRelation(Model $model): Primitive|Item\n{\n return $model->relation ? $this->item($model->relation, new RelationTransformer()) : $this->primitive(null)\n}\n")),(0,r.kt)("h2",{id:"response-payload"},"Response Payload"),(0,r.kt)("p",null,"You have the flexibility to define your own custom response payload or utilize one of the supported serializers.\nSerializer classes let you switch between various output formats with minimal effect on your Transformers."),(0,r.kt)("p",null,"Current ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/serializers/"},"supported serializers"),":"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"ArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"DataArraySerializer")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"JsonApiSerializer"))),(0,r.kt)("p",null,"To modify the default Fractal serializer,\naccess the ",(0,r.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/fractal.php")," configuration file\nand update the ",(0,r.kt)("inlineCode",{parentName:"p"},"default_serializer")," setting to your preferred serializer."),(0,r.kt)("p",null,"By default, Apiato uses ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer"),".\nThis serializer is not to everyone\u2019s tastes, because it adds a ",(0,r.kt)("inlineCode",{parentName:"p"},"data")," namespace to the output.\nA very basic response of the ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," will look like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": {\n "object": "User",\n "id": "XbPW7awNkzl83LD6",\n "name": "Mohammad Alavi"\n }\n}\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"DataArraySerializer")," is handy because it allows space for ",(0,r.kt)("inlineCode",{parentName:"p"},"meta")," data\n(like pagination, or totals) in both Items and Collections."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "data": [ ... ],\n "meta": {\n "include": [\n "xxx",\n "yyy"\n ],\n "custom": [],\n "pagination": {\n "total": 999,\n "count": 999,\n "per_page": 999,\n "current_page": 999,\n "total_pages": 999,\n "links": {\n "next": "http://api.apiato.test/v1/accounts?page=999"\n }\n }\n }\n}\n')),(0,r.kt)("admonition",{title:"Further Reading",type:"info"},(0,r.kt)("p",{parentName:"admonition"},"For more detailed information, please refer to ",(0,r.kt)("a",{parentName:"p",href:"https://fractal.thephpleague.com/transformers/"},"Fractal")," and ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/spatie/laravel-fractal"},"Laravel Fractal Wrapper")," documentations.")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e8f086b4.96f925be.js b/assets/js/e8f086b4.96f925be.js new file mode 100644 index 000000000..c70a7781c --- /dev/null +++ b/assets/js/e8f086b4.96f925be.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[6462],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),p=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=a,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||o;return n?r.createElement(h,i(i({ref:t},u),{},{components:n})):r.createElement(h,i({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:a,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=n(87462),a=(n(67294),n(3905));const o={sidebar_position:7,title:"Hash ID"},i=void 0,s={unversionedId:"security/hash-id",id:"version-12.x/security/hash-id",title:"Hash ID",description:"Hashing your internal ID's is a very helpful feature for many security reasons,",source:"@site/versioned_docs/version-12.x/security/hash-id.md",sourceDirName:"security",slug:"/security/hash-id",permalink:"/docs/security/hash-id",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/security/hash-id.md",tags:[],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1704287783,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Hash ID"},sidebar:"tutorialSidebar",previous:{title:"Password Reset",permalink:"/docs/security/password-reset"},next:{title:"Overview",permalink:"/docs/pacakges/"}},l={},p=[{value:"Enabling Hash ID",id:"enabling-hash-id",level:2},{value:"Usage",id:"usage",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Route Model Binding",id:"route-model-binding",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Hashing your internal ID's is a very helpful feature for many security reasons,\nsuch as preventing your internal ID's from being exposed to the public, your competitors, and hackers."),(0,a.kt)("h2",{id:"enabling-hash-id"},"Enabling Hash ID"),(0,a.kt)("p",null,"Set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID=true")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"BCMath PHP Extension is required to use this feature.")),(0,a.kt)("p",null,"Make sure to always use the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method on any model,\nwhenever you need to return an ID (mainly from ",(0,a.kt)("a",{parentName:"p",href:"/docs/components/main-components/transformers"},"transformers"),")\nweather you are using Hash ID or not.\nIf Hash ID feature is disabled, the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method will return the normal ID."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"'id' => $user->getHashedKey(),\n")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"There are three ways to pass an ID to your system via the API:"),(0,a.kt)("p",null,"In URL:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items/XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"As query string:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items?id=XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"Or as HTTP request body:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,a.kt)("p",null,"Now you need to tell your API to ",(0,a.kt)("a",{parentName:"p",href:"/docs/components/main-components/requests#request-properties"},"decode the ID")," for you.\nThis is done by setting the ",(0,a.kt)("inlineCode",{parentName:"p"},"$decode")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"$urlParameters")," properties on your Request class.\nAfter setting those properties,\nthe ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller."),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},(0,a.kt)("inlineCode",{parentName:"p"},"$request->id")," will return the decoded ID.")),(0,a.kt)("h2",{id:"configuration"},"Configuration"),(0,a.kt)("p",null,"Hash ID configuration is done in the ",(0,a.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/hashids.php")," config file.\nYou can set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file to any random string.\nApiato defaults to the ",(0,a.kt)("inlineCode",{parentName:"p"},"APP_KEY")," should this not be set."),(0,a.kt)("admonition",{type:"danger"},(0,a.kt)("p",{parentName:"admonition"},"The ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," acts as the salt during hashing of the ID. This should never be changed in production\nas it renders all previously generated IDs impossible to decode.")),(0,a.kt)("h2",{id:"route-model-binding"},"Route Model Binding"),(0,a.kt)("p",null,"Laravel ",(0,a.kt)("a",{parentName:"p",href:"https://laravel.com/docs/routing#route-model-binding"},"Route Model Binding")," feature is supported out of the box and Apiato will automatically decode the ID for you."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e8f086b4.c647df74.js b/assets/js/e8f086b4.c647df74.js deleted file mode 100644 index 23cd12eb4..000000000 --- a/assets/js/e8f086b4.c647df74.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[6462],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var r=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),p=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=p(n),m=a,h=d["".concat(l,".").concat(m)]||d[m]||c[m]||o;return n?r.createElement(h,i(i({ref:t},u),{},{components:n})):r.createElement(h,i({ref:t},u))}));function h(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:a,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=n(87462),a=(n(67294),n(3905));const o={sidebar_position:7,title:"Hash ID"},i=void 0,s={unversionedId:"security/hash-id",id:"version-12.x/security/hash-id",title:"Hash ID",description:"Hashing your internal ID's is a very helpful feature for many security reasons,",source:"@site/versioned_docs/version-12.x/security/hash-id.md",sourceDirName:"security",slug:"/security/hash-id",permalink:"/docs/security/hash-id",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/security/hash-id.md",tags:[],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",sidebarPosition:7,frontMatter:{sidebar_position:7,title:"Hash ID"},sidebar:"tutorialSidebar",previous:{title:"Password Reset",permalink:"/docs/security/password-reset"},next:{title:"Overview",permalink:"/docs/pacakges/"}},l={},p=[{value:"Enabling Hash ID",id:"enabling-hash-id",level:2},{value:"Usage",id:"usage",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Route Model Binding",id:"route-model-binding",level:2}],u={toc:p},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,r.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Hashing your internal ID's is a very helpful feature for many security reasons,\nsuch as preventing your internal ID's from being exposed to the public, your competitors, and hackers."),(0,a.kt)("h2",{id:"enabling-hash-id"},"Enabling Hash ID"),(0,a.kt)("p",null,"Set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID=true")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file."),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"BCMath PHP Extension is required to use this feature.")),(0,a.kt)("p",null,"Make sure to always use the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method on any model,\nwhenever you need to return an ID (mainly from ",(0,a.kt)("a",{parentName:"p",href:"/docs/components/main-components/transformers"},"transformers"),")\nweather you are using Hash ID or not.\nIf Hash ID feature is disabled, the ",(0,a.kt)("inlineCode",{parentName:"p"},"getHashedKey")," method will return the normal ID."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"'id' => $user->getHashedKey(),\n")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"There are three ways to pass an ID to your system via the API:"),(0,a.kt)("p",null,"In URL:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items/XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"As query string:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"/items?id=XbPW7awNkzl83LD6\n")),(0,a.kt)("p",null,"Or as HTTP request body:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "id": "XbPW7awNkzl83LD6"\n}\n')),(0,a.kt)("p",null,"Now you need to tell your API to ",(0,a.kt)("a",{parentName:"p",href:"/docs/components/main-components/requests#request-properties"},"decode the ID")," for you.\nThis is done by setting the ",(0,a.kt)("inlineCode",{parentName:"p"},"decode")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"urlParameters")," properties on your Request class.\nAfter setting those properties,\nthe ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller."),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},(0,a.kt)("inlineCode",{parentName:"p"},"$request->id")," will return the decoded ID.")),(0,a.kt)("h2",{id:"configuration"},"Configuration"),(0,a.kt)("p",null,"Hash ID configuration is done in the ",(0,a.kt)("inlineCode",{parentName:"p"},"app/Ship/Configs/hashids.php")," config file.\nYou can set the ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file to any random string.\nApiato defaults to the ",(0,a.kt)("inlineCode",{parentName:"p"},"APP_KEY")," should this not be set."),(0,a.kt)("admonition",{type:"danger"},(0,a.kt)("p",{parentName:"admonition"},"The ",(0,a.kt)("inlineCode",{parentName:"p"},"HASH_ID_KEY")," acts as the salt during hashing of the ID. This should never be changed in production\nas it renders all previously generated IDs impossible to decode.")),(0,a.kt)("h2",{id:"route-model-binding"},"Route Model Binding"),(0,a.kt)("p",null,"Laravel ",(0,a.kt)("a",{parentName:"p",href:"https://laravel.com/docs/routing#route-model-binding"},"Route Model Binding")," feature is supported out of the box and Apiato will automatically decode the ID for you."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/main.2e92fe40.js b/assets/js/main.a4e99853.js similarity index 99% rename from assets/js/main.2e92fe40.js rename to assets/js/main.a4e99853.js index b4915febe..5d943fb5e 100644 --- a/assets/js/main.2e92fe40.js +++ b/assets/js/main.a4e99853.js @@ -1,2 +1,2 @@ -/*! For license information please see main.2e92fe40.js.LICENSE.txt */ -(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[179],{20830:(e,t,n)=>{"use strict";n.d(t,{W:()=>a});var o=n(67294);function a(){return o.createElement("svg",{width:"20",height:"20",className:"DocSearch-Search-Icon",viewBox:"0 0 20 20"},o.createElement("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}},723:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m});var o=n(67294),a=n(87462),r=n(68356),s=n.n(r),i=n(16887);const c={"01255979":[()=>n.e(8928).then(n.bind(n,13717)),"@site/versioned_docs/version-10.x/getting-started/software-architectural-patterns.md",13717],"018bfed5":[()=>n.e(6329).then(n.bind(n,71736)),"@site/versioned_docs/version-11.x/additional-features/overview.md",71736],"023af9ff":[()=>n.e(8866).then(n.bind(n,43007)),"@site/versioned_docs/version-12.x/components/optional-components/service-providers.md",43007],"02dc5f66":[()=>n.e(1445).then(n.bind(n,15909)),"@site/versioned_docs/version-12.x/framework-features/rate-limiting.md",15909],"02e9e60d":[()=>n.e(5384).then(n.bind(n,2987)),"@site/versioned_docs/version-9.x/core-features/useful-commands.md",2987],"03d23ece":[()=>n.e(7887).then(n.bind(n,95916)),"@site/versioned_docs/version-10.x/core-features/authorization.md",95916],"044a7006":[()=>n.e(7861).then(n.bind(n,91080)),"@site/versioned_docs/version-12.x/getting-started/customized-laravel-components.md",91080],"05614d2f":[()=>n.e(6055).then(n.t.bind(n,10767,19)),"~docs/default/tag-docs-next-tags-mail-357.json",10767],"0688ea12":[()=>n.e(3616).then(n.bind(n,97889)),"@site/versioned_docs/version-10.x/core-features/validation.md",97889],"06abd5b6":[()=>n.e(3887).then(n.bind(n,59505)),"@site/docs/security/registration.mdx",59505],"0a783a8b":[()=>n.e(8636).then(n.bind(n,56092)),"@site/versioned_docs/version-10.x/contribution-guide.md",56092],"0aef6e2f":[()=>n.e(4612).then(n.bind(n,62943)),"@site/versioned_docs/version-11.x/getting-started/responses.md",62943],"0b3ceb4d":[()=>n.e(71).then(n.t.bind(n,83769,19)),"/home/runner/work/documentation/documentation/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",83769],"0bfb57e8":[()=>n.e(2246).then(n.bind(n,31534)),"@site/versioned_docs/version-9.x/getting-started/requests.md",31534],"0c2c09cf":[()=>n.e(3469).then(n.bind(n,85562)),"@site/docs/security/email-varification.md",85562],"0d59295f":[()=>n.e(6432).then(n.t.bind(n,92053,19)),"~docs/default/tag-docs-tags-container-5f2.json",92053],"0d75c27e":[()=>n.e(2186).then(n.t.bind(n,5399,19)),"~docs/default/tag-docs-tags-etag-49c.json",5399],"0d8578a6":[()=>n.e(5706).then(n.bind(n,50148)),"@site/versioned_docs/version-10.x/getting-started/requests.md",50148],"0e1a79a5":[()=>n.e(2080).then(n.bind(n,18116)),"@site/versioned_docs/version-12.x/components/main-components/actions.md",18116],"0fdfb154":[()=>n.e(1813).then(n.bind(n,65593)),"@site/versioned_docs/version-12.x/components/optional-components/policies.md",65593],10691446:[()=>n.e(6306).then(n.t.bind(n,40125,19)),"~docs/default/tag-docs-next-tags-value-306.json",40125],"10f6dc74":[()=>n.e(3935).then(n.bind(n,87113)),"@site/docs/components/main-components/routes.md",87113],"114e21b0":[()=>n.e(5680).then(n.t.bind(n,9736,19)),"~docs/default/tag-docs-next-tags-profiler-9a1.json",9736],"128066bb":[()=>n.e(6570).then(n.bind(n,22730)),"@site/versioned_docs/version-12.x/components/main-components/views.md",22730],"13140ae3":[()=>n.e(2039).then(n.bind(n,41203)),"@site/versioned_docs/version-11.x/optional-components/tests.md",41203],"136a2cf4":[()=>n.e(1367).then(n.bind(n,3795)),"@site/versioned_docs/version-9.x/core-features/hash-id.md",3795],"13d06b8c":[()=>n.e(7617).then(n.bind(n,91226)),"@site/versioned_docs/version-12.x/security/authorization.mdx",91226],"13f612ac":[()=>n.e(1777).then(n.t.bind(n,74993,19)),"~docs/default/tag-docs-next-tags-repository-82e.json",74993],"144669f6":[()=>n.e(4060).then(n.bind(n,26617)),"@site/docs/pacakges/documentation.md",26617],"14935a59":[()=>n.e(1498).then(n.t.bind(n,15336,19)),"~docs/default/tag-docs-next-tags-rate-limiting-d7b.json",15336],"149faf5c":[()=>n.e(3431).then(n.bind(n,48641)),"@site/versioned_docs/version-11.x/optional-components/commands.md",48641],"15e12a2f":[()=>n.e(3380).then(n.bind(n,72532)),"@site/docs/components/main-components/models.md",72532],"1626590c":[()=>n.e(5378).then(n.bind(n,15915)),"@site/versioned_docs/version-9.x/upgrade-guide.md",15915],"1647ec57":[()=>n.e(8529).then(n.bind(n,63153)),"@site/versioned_docs/version-9.x/core-features/social-authentication.md",63153],"1675a853":[()=>n.e(9845).then(n.bind(n,76878)),"@site/versioned_docs/version-10.x/getting-started/requirements.md",76878],"1746ec49":[()=>n.e(617).then(n.t.bind(n,67,19)),"~docs/default/tag-docs-tags-porto-553.json",67],17896441:[()=>Promise.all([n.e(532),n.e(7918)]).then(n.bind(n,97895)),"@theme/DocItem",97895],"18c33990":[()=>n.e(668).then(n.bind(n,92438)),"@site/versioned_docs/version-9.x/optional-components/providers.md",92438],"18ee9ead":[()=>n.e(8271).then(n.bind(n,30390)),"@site/versioned_docs/version-11.x/core-features/rate-limiting.md",30390],19031211:[()=>n.e(6890).then(n.bind(n,5973)),"@site/versioned_docs/version-11.x/main-components/controllers.md",5973],"1958aa06":[()=>n.e(851).then(n.t.bind(n,52921,19)),"~docs/default/tag-docs-next-tags-transformer-ad9.json",52921],"19bbdee1":[()=>n.e(2318).then(n.bind(n,23896)),"@site/versioned_docs/version-9.x/getting-started/markdown-features.mdx",23896],"1abe76cc":[()=>n.e(73).then(n.bind(n,28936)),"@site/versioned_docs/version-9.x/getting-started/responses.md",28936],"1b07933a":[()=>n.e(8861).then(n.bind(n,6386)),"@site/versioned_docs/version-10.x/getting-started/conventions-and-principles.md",6386],"1b1d605d":[()=>n.e(6451).then(n.bind(n,37446)),"@site/versioned_docs/version-12.x/components/optional-components/notifications.md",37446],"1b728867":[()=>n.e(251).then(n.bind(n,4031)),"@site/versioned_docs/version-11.x/optional-components/notifications.md",4031],"1be78505":[()=>Promise.all([n.e(532),n.e(9514)]).then(n.bind(n,19963)),"@theme/DocPage",19963],"1c2e3411":[()=>n.e(3042).then(n.t.bind(n,20681,19)),"~docs/default/tag-docs-next-tags-task-018.json",20681],"1c647d6d":[()=>n.e(2694).then(n.bind(n,11238)),"@site/versioned_docs/version-10.x/main-components/subactions.md",11238],"1c74e866":[()=>n.e(6415).then(n.t.bind(n,99947,19)),"~docs/default/tag-docs-tags-command-d40.json",99947],"1d2ace06":[()=>n.e(7152).then(n.bind(n,39694)),"@site/versioned_docs/version-12.x/components/optional-components/configs.md",39694],"1d502ec8":[()=>n.e(872).then(n.bind(n,48109)),"@site/versioned_docs/version-9.x/getting-started/conventions-and-principles.md",48109],"1db39f58":[()=>n.e(3022).then(n.bind(n,66174)),"@site/versioned_docs/version-12.x/components/optional-components/tests.md",66174],"1ebbf8ad":[()=>n.e(4509).then(n.t.bind(n,85776,19)),"~docs/default/tag-docs-next-tags-action-11a.json",85776],"1f4f2990":[()=>n.e(1626).then(n.bind(n,60804)),"@site/versioned_docs/version-12.x/getting-started/installation.mdx",60804],"1f987ab0":[()=>n.e(1428).then(n.bind(n,30361)),"@site/versioned_docs/version-12.x/components/optional-components/factories.md",30361],"1fa6cfaa":[()=>n.e(4276).then(n.t.bind(n,24520,19)),"~docs/default/tag-docs-next-tags-seeder-1ff.json",24520],20702474:[()=>n.e(716).then(n.bind(n,53625)),"@site/versioned_docs/version-10.x/optional-components/tests.md",53625],"20bb4ef4":[()=>n.e(9537).then(n.bind(n,19720)),"@site/versioned_docs/version-11.x/main-components/requests.md",19720],"22ecef17":[()=>n.e(9928).then(n.t.bind(n,44239,19)),"~docs/default/tag-docs-tags-migration-f8a.json",44239],"23c8336c":[()=>n.e(4753).then(n.bind(n,71526)),"@site/versioned_docs/version-9.x/core-features/payments.md",71526],"2457aad7":[()=>n.e(3038).then(n.bind(n,6654)),"@site/versioned_docs/version-11.x/main-components/tasks.md",6654],"245b95ea":[()=>n.e(7267).then(n.bind(n,13788)),"@site/versioned_docs/version-10.x/core-features/data-caching.md",13788],"245df310":[()=>n.e(6465).then(n.t.bind(n,10202,19)),"~docs/default/tag-docs-next-tags-testing-dcd.json",10202],"2529077f":[()=>n.e(9356).then(n.bind(n,81107)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/debugger.md",81107],"26084ccc":[()=>n.e(841).then(n.bind(n,55945)),"@site/versioned_docs/version-10.x/optional-components/migrations.md",55945],"27268bf4":[()=>n.e(5267).then(n.bind(n,7710)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/overview.md",7710],"27299a3b":[()=>n.e(4993).then(n.bind(n,6577)),"@site/versioned_docs/version-9.x/getting-started/installation.md",6577],"27c465e4":[()=>n.e(9301).then(n.bind(n,23613)),"@site/versioned_docs/version-11.x/core-features/pagination.md",23613],"28055a57":[()=>n.e(768).then(n.bind(n,38601)),"@site/versioned_docs/version-11.x/optional-components/factories.md",38601],"28451e1a":[()=>n.e(50).then(n.bind(n,86528)),"@site/versioned_docs/version-10.x/core-features/hash-id.md",86528],"28a91100":[()=>n.e(258).then(n.bind(n,18884)),"@site/versioned_docs/version-12.x/framework-features/code-generator.md",18884],"28b502c2":[()=>n.e(3281).then(n.bind(n,18402)),"@site/versioned_docs/version-12.x/framework-features/profiler.md",18402],"29d4a56b":[()=>n.e(13).then(n.bind(n,67649)),"@site/docs/architecture-concepts/request-lifecycle.md",67649],"2a15d269":[()=>n.e(6223).then(n.bind(n,61233)),"@site/versioned_docs/version-11.x/miscellaneous/tasks-scheduling.md",61233],"2a5e16ec":[()=>n.e(5577).then(n.bind(n,99483)),"@site/versioned_docs/version-10.x/core-features/query-parameters.md",99483],"2aaa4378":[()=>n.e(8136).then(n.bind(n,65374)),"@site/versioned_docs/version-9.x/optional-components/middlewares.md",65374],"2ac485cd":[()=>n.e(6872).then(n.bind(n,57217)),"@site/versioned_docs/version-9.x/main-components/transformers.md",57217],"2af38141":[()=>n.e(2347).then(n.bind(n,52766)),"@site/docs/framework-features/api-versioning.md",52766],"2b026185":[()=>n.e(1021).then(n.t.bind(n,27239,19)),"~docs/default/tag-docs-tags-view-69f.json",27239],"2c32570e":[()=>n.e(9253).then(n.bind(n,83686)),"@site/versioned_docs/version-9.x/core-features/query-parameters.md",83686],"2c8be122":[()=>n.e(9423).then(n.t.bind(n,57190,19)),"~docs/default/tag-docs-next-tags-event-c19.json",57190],"2ddf079f":[()=>n.e(2263).then(n.t.bind(n,44906,19)),"~docs/default/tag-docs-next-tags-model-475.json",44906],"2e3626b2":[()=>n.e(2827).then(n.bind(n,28170)),"@site/docs/components/main-components/requests.md",28170],"2e6078dc":[()=>n.e(1331).then(n.t.bind(n,50687,19)),"~docs/default/tag-docs-tags-profiler-41d.json",50687],"2f1df01f":[()=>n.e(9752).then(n.bind(n,86943)),"@site/versioned_docs/version-12.x/framework-features/etag.md",86943],"2f3ae6a8":[()=>n.e(3763).then(n.t.bind(n,34590,19)),"~docs/default/tag-docs-tags-helper-450.json",34590],"2f4205f9":[()=>n.e(8933).then(n.bind(n,90961)),"@site/versioned_docs/version-12.x/components/main-components/controllers.md",90961],"2f6f9645":[()=>n.e(9204).then(n.bind(n,18856)),"@site/versioned_docs/version-10.x/optional-components/middlewares.md",18856],"303f2916":[()=>n.e(3207).then(n.bind(n,49735)),"@site/versioned_docs/version-12.x/components/optional-components/jobs.md",49735],"30644fd9":[()=>n.e(3821).then(n.bind(n,20478)),"@site/versioned_docs/version-12.x/prologue/contribution-guide.md",20478],"30dc594d":[()=>n.e(6392).then(n.bind(n,29917)),"@site/versioned_docs/version-10.x/faq.md",29917],"31053c79":[()=>n.e(5918).then(n.bind(n,43042)),"@site/versioned_docs/version-11.x/main-components/actions.md",43042],31874089:[()=>n.e(6050).then(n.bind(n,49493)),"@site/versioned_docs/version-9.x/core-features/admin-dashboard.md",49493],31910907:[()=>n.e(370).then(n.bind(n,31211)),"@site/versioned_docs/version-10.x/optional-components/criterias.md",31211],"31a3eb6f":[()=>n.e(7170).then(n.bind(n,87447)),"@site/versioned_docs/version-9.x/optional-components/languages.md",87447],"321c3473":[()=>n.e(1062).then(n.bind(n,31584)),"@site/versioned_docs/version-10.x/main-components/requests.md",31584],"327f2b8d":[()=>n.e(2308).then(n.bind(n,81387)),"@site/versioned_docs/version-11.x/core-features/hash-id.md",81387],"329f7802":[()=>n.e(8947).then(n.t.bind(n,3917,19)),"~docs/default/tag-docs-next-tags-component-6a0.json",3917],"332d52b4":[()=>n.e(6866).then(n.t.bind(n,81418,19)),"~docs/default/tag-docs-tags-action-57a.json",81418],"33939c05":[()=>n.e(3810).then(n.t.bind(n,83007,19)),"~docs/default/tag-docs-tags-value-67d.json",83007],"33b191bc":[()=>n.e(4185).then(n.bind(n,10888)),"@site/versioned_docs/version-9.x/miscellaneous/tests-helpers.md",10888],"33f68dcb":[()=>n.e(4865).then(n.bind(n,76207)),"@site/versioned_docs/version-11.x/getting-started/requests.md",76207],"35891e33":[()=>n.e(8700).then(n.t.bind(n,73735,19)),"~docs/default/tag-docs-tags-code-generator-8a2.json",73735],"360898bf":[()=>n.e(8563).then(n.bind(n,23520)),"@site/docs/components/optional-components/events.md",23520],"36ca3315":[()=>n.e(3448).then(n.bind(n,5857)),"@site/versioned_docs/version-10.x/core-features/etag.md",5857],"3720c009":[()=>Promise.all([n.e(532),n.e(3751)]).then(n.bind(n,9861)),"@theme/DocTagsListPage",9861],"374b0855":[()=>n.e(298).then(n.bind(n,16269)),"@site/versioned_docs/version-10.x/core-features/pagination.md",16269],37833312:[()=>n.e(7725).then(n.bind(n,37110)),"@site/versioned_docs/version-9.x/main-components/requests.md",37110],"38a9fada":[()=>n.e(6364).then(n.bind(n,3426)),"@site/docs/components/optional-components/repository/criterias.md",3426],"38e3e5e2":[()=>n.e(3288).then(n.bind(n,30607)),"@site/versioned_docs/version-11.x/getting-started/conventions-and-principles.md",30607],"3919a682":[()=>n.e(7736).then(n.bind(n,92964)),"@site/versioned_docs/version-11.x/main-components/models.md",92964],"3a8a9a02":[()=>n.e(5756).then(n.bind(n,17244)),"@site/versioned_docs/version-11.x/upgrade-guide.md",17244],"3b074962":[()=>n.e(7669).then(n.bind(n,49710)),"@site/versioned_docs/version-10.x/optional-components/helpers.md",49710],"3b382b4c":[()=>n.e(7204).then(n.bind(n,35433)),"@site/docs/components/optional-components/seeders.md",35433],"3c75101b":[()=>n.e(6004).then(n.bind(n,56635)),"@site/versioned_docs/version-11.x/core-features/profiler.md",56635],"3c8cc128":[()=>n.e(8886).then(n.bind(n,2608)),"@site/versioned_docs/version-9.x/core-features/requests-monitor.md",2608],"3ce8e004":[()=>n.e(2200).then(n.bind(n,44441)),"@site/versioned_docs/version-11.x/miscellaneous/tests-helpers.md",44441],"3d9aab4f":[()=>n.e(3928).then(n.bind(n,78604)),"@site/versioned_docs/version-12.x/getting-started/best-practices.md",78604],"3dd44646":[()=>n.e(9618).then(n.t.bind(n,52144,19)),"~docs/default/tag-docs-next-tags-authorization-798.json",52144],"3f06413e":[()=>n.e(4295).then(n.bind(n,87147)),"@site/docs/consulting.md",87147],"3fa6a9b5":[()=>n.e(2212).then(n.bind(n,63887)),"@site/versioned_docs/version-11.x/optional-components/helpers.md",63887],"3ffc09c4":[()=>n.e(348).then(n.bind(n,42213)),"@site/docs/security/authorization.mdx",42213],"4015baac":[()=>n.e(7442).then(n.t.bind(n,82305,19)),"~docs/default/tag-docs-next-tags-sub-action-9b6.json",82305],"40185eb5":[()=>n.e(6879).then(n.bind(n,60181)),"@site/versioned_docs/version-9.x/miscellaneous/tasks-scheduling.md",60181],"4047e3d8":[()=>n.e(2428).then(n.t.bind(n,22381,19)),"~docs/default/tag-docs-tags-notification-fa4.json",22381],"40d100db":[()=>n.e(8652).then(n.bind(n,20670)),"@site/versioned_docs/version-10.x/upgrade-guide.md",20670],"411cd95c":[()=>n.e(7745).then(n.t.bind(n,76516,19)),"~docs/default/tag-docs-tags-request-ac8.json",76516],"415f74f4":[()=>n.e(1136).then(n.t.bind(n,49468,19)),"~docs/default/tag-docs-tags-queue-17d.json",49468],"41e9fc02":[()=>n.e(6605).then(n.bind(n,18044)),"@site/docs/components/optional-components/configs.md",18044],"42656b86":[()=>n.e(6082).then(n.bind(n,27763)),"@site/docs/components/optional-components/factories.md",27763],"427fd2ae":[()=>n.e(6999).then(n.bind(n,84325)),"@site/versioned_docs/version-9.x/getting-started/overview.md",84325],"43ff30f2":[()=>n.e(590).then(n.t.bind(n,14437,19)),"~docs/default/tag-docs-next-tags-response-604.json",14437],"44a2c628":[()=>n.e(3483).then(n.t.bind(n,77150,19)),"~docs/default/tag-docs-next-tags-test-adf.json",77150],"4504fa21":[()=>n.e(4095).then(n.bind(n,34843)),"@site/versioned_docs/version-10.x/optional-components/jobs.md",34843],"450e6318":[()=>n.e(468).then(n.bind(n,70592)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/settings.md",70592],"45778f4a":[()=>n.e(2974).then(n.bind(n,46477)),"@site/versioned_docs/version-12.x/architecture-concepts/components.md",46477],"4610191c":[()=>n.e(7422).then(n.bind(n,24202)),"@site/versioned_docs/version-12.x/pacakges/documentation.md",24202],"4683e51a":[()=>n.e(915).then(n.bind(n,92482)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/localization.md",92482],"4707ca8c":[()=>n.e(1676).then(n.bind(n,14967)),"@site/versioned_docs/version-11.x/optional-components/events.md",14967],"47355b93":[()=>n.e(7213).then(n.bind(n,38647)),"@site/docs/pacakges/localization.md",38647],"47a03c7f":[()=>n.e(1606).then(n.t.bind(n,25458,19)),"~docs/default/tag-docs-tags-testing-194.json",25458],"4890df88":[()=>n.e(9512).then(n.t.bind(n,52936,19)),"~docs/default/tag-docs-tags-criteria-b71.json",52936],"491bc18c":[()=>n.e(4323).then(n.bind(n,12061)),"@site/versioned_docs/version-11.x/main-components/transformers.md",12061],"4a272307":[()=>n.e(1330).then(n.t.bind(n,1718,19)),"~docs/default/tag-docs-next-tags-etag-407.json",1718],"4a69c104":[()=>n.e(3892).then(n.t.bind(n,41838,19)),"~docs/default/tag-docs-next-tags-factory-b02.json",41838],"4b4e43c1":[()=>n.e(2599).then(n.bind(n,82602)),"@site/versioned_docs/version-12.x/components/main-components/subactions.md",82602],"4bf0522b":[()=>n.e(7452).then(n.t.bind(n,30476,19)),"~docs/default/tag-docs-tags-authorization-ed9.json",30476],"4ce02702":[()=>n.e(2845).then(n.bind(n,57184)),"@site/docs/components/optional-components/migrations.md",57184],"4d0c2aca":[()=>n.e(799).then(n.bind(n,8930)),"@site/versioned_docs/version-11.x/getting-started/samples.md",8930],"4d3c06b1":[()=>n.e(7060).then(n.t.bind(n,3749,19)),"~docs/default/tag-docs-next-tags-code-generator-45b.json",3749],"4d7c6870":[()=>n.e(5007).then(n.bind(n,22196)),"@site/versioned_docs/version-12.x/security/password-reset.md",22196],"4d96365a":[()=>Promise.all([n.e(532),n.e(280)]).then(n.bind(n,1e4)),"@site/docs/components/optional-components/index.md",1e4],"4e202c12":[()=>n.e(7673).then(n.bind(n,16967)),"@site/versioned_docs/version-11.x/optional-components/repositories.md",16967],"4ee994fc":[()=>n.e(823).then(n.bind(n,75745)),"@site/versioned_docs/version-9.x/core-features/rate-limiting.md",75745],"5040bcd3":[()=>n.e(6147).then(n.bind(n,53871)),"@site/versioned_docs/version-12.x/components/main-components/routes.md",53871],"518c31a4":[()=>n.e(6586).then(n.bind(n,31456)),"@site/versioned_docs/version-9.x/core-features/api-docs-generator.md",31456],"525560b7":[()=>n.e(7576).then(n.bind(n,63403)),"@site/docs/security/password-reset.md",63403],"53df34ca":[()=>n.e(542).then(n.t.bind(n,56403,19)),"~docs/default/tag-docs-tags-main-component-096.json",56403],"53f60aaa":[()=>n.e(8172).then(n.bind(n,77079)),"@site/versioned_docs/version-12.x/architecture-concepts/container.md",77079],"53fc899c":[()=>n.e(6126).then(n.bind(n,7798)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/documentation.md",7798],"53ff84c0":[()=>n.e(9970).then(n.bind(n,16478)),"@site/versioned_docs/version-12.x/components/optional-components/seeders.md",16478],54071611:[()=>n.e(9783).then(n.bind(n,13845)),"@site/versioned_docs/version-10.x/getting-started/installation.md",13845],"540a99bf":[()=>n.e(9203).then(n.bind(n,21614)),"@site/docs/components/main-components/subactions.md",21614],54828236:[()=>n.e(5963).then(n.bind(n,82829)),"@site/versioned_docs/version-11.x/optional-components/providers.md",82829],"55960ee5":[()=>n.e(4121).then(n.t.bind(n,88070,19)),"~docs/default/tags-list-current-prop-15a.json",88070],"55ea0897":[()=>n.e(8053).then(n.bind(n,73616)),"@site/versioned_docs/version-11.x/optional-components/languages.md",73616],"568e42b9":[()=>n.e(4621).then(n.bind(n,9618)),"@site/docs/security/authentication.mdx",9618],"57aa3e64":[()=>n.e(1143).then(n.bind(n,15681)),"@site/versioned_docs/version-12.x/security/authentication.mdx",15681],"58f8f5d8":[()=>n.e(9724).then(n.bind(n,6045)),"@site/versioned_docs/version-11.x/optional-components/values.md",6045],"59ee4ecb":[()=>n.e(1752).then(n.bind(n,60500)),"@site/versioned_docs/version-11.x/additional-features/documentation.md",60500],"5a824d24":[()=>n.e(6882).then(n.bind(n,72051)),"@site/versioned_docs/version-9.x/getting-started/software-architectural-patterns.md",72051],"5ac833b0":[()=>n.e(4948).then(n.bind(n,34137)),"@site/versioned_docs/version-9.x/optional-components/repositories.md",34137],"5ba01c12":[()=>n.e(7025).then(n.bind(n,18070)),"@site/versioned_docs/version-9.x/core-features/authorization.md",18070],"5e76b55a":[()=>n.e(5697).then(n.bind(n,44307)),"@site/versioned_docs/version-12.x/components/optional-components/mail.md",44307],"5e923e9d":[()=>n.e(5471).then(n.t.bind(n,90726,19)),"~docs/default/tag-docs-tags-service-provider-fb1.json",90726],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,36809)),"@generated/docusaurus.config",36809],"5ea00916":[()=>n.e(5141).then(n.bind(n,88443)),"@site/versioned_docs/version-10.x/main-components/controllers.md",88443],"5ecea96b":[()=>n.e(5332).then(n.bind(n,35415)),"@site/versioned_docs/version-12.x/architecture-concepts/readme.md",35415],"60321f1e":[()=>n.e(8789).then(n.bind(n,37636)),"@site/versioned_docs/version-10.x/main-components/actions.md",37636],"6095db2e":[()=>n.e(9434).then(n.bind(n,40067)),"@site/versioned_docs/version-10.x/optional-components/mails.md",40067],"61086f7d":[()=>n.e(5656).then(n.bind(n,74914)),"@site/versioned_docs/version-9.x/optional-components/values.md",74914],"6127c255":[()=>n.e(3275).then(n.bind(n,14648)),"@site/versioned_docs/version-9.x/optional-components/criterias.md",14648],"62e78712":[()=>n.e(3499).then(n.bind(n,89046)),"@site/docs/components/optional-components/mail.md",89046],"630dcb4d":[()=>n.e(4579).then(n.bind(n,55152)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/social-authentication.md",55152],"63cffe9f":[()=>n.e(9144).then(n.bind(n,42051)),"@site/versioned_docs/version-11.x/contribution-guide.md",42051],"6459b84b":[()=>n.e(7868).then(n.bind(n,82424)),"@site/docs/getting-started/installation.mdx",82424],"653187f9":[()=>n.e(6652).then(n.bind(n,12806)),"@site/versioned_docs/version-9.x/core-features/profiler.md",12806],"66b63dfc":[()=>n.e(5828).then(n.bind(n,6378)),"@site/docs/getting-started/best-practices.md",6378],"66d590de":[()=>n.e(9197).then(n.bind(n,71263)),"@site/versioned_docs/version-12.x/components/main-components/requests.md",71263],"6777c82a":[()=>n.e(5601).then(n.t.bind(n,34869,19)),"~docs/default/tag-docs-tags-api-versioning-d39.json",34869],"67ad9935":[()=>n.e(2693).then(n.t.bind(n,17767,19)),"~docs/default/tag-docs-tags-factory-95b.json",17767],"6855260d":[()=>n.e(3533).then(n.t.bind(n,87060,19)),"~docs/default/tag-docs-next-tags-migration-66f.json",87060],"687b8652":[()=>n.e(8578).then(n.bind(n,35842)),"@site/versioned_docs/version-12.x/components/optional-components/migrations.md",35842],"693f6ee0":[()=>n.e(1024).then(n.bind(n,25638)),"@site/versioned_docs/version-10.x/optional-components/repositories.md",25638],"69995e4b":[()=>n.e(3618).then(n.t.bind(n,58776,19)),"~docs/default/tag-docs-next-tags-config-ecf.json",58776],"69bce674":[()=>n.e(2997).then(n.t.bind(n,55888,19)),"~docs/default/tag-docs-tags-listener-ce0.json",55888],"6aabc5ba":[()=>n.e(4218).then(n.bind(n,74076)),"@site/docs/framework-features/rbac.md",74076],"6ca79f64":[()=>n.e(893).then(n.bind(n,94808)),"@site/versioned_docs/version-11.x/miscellaneous/tasks-queuing.md",94808],"6e7ac859":[()=>n.e(776).then(n.bind(n,54359)),"@site/versioned_docs/version-11.x/core-features/user-registration.md",54359],"70943eb8":[()=>n.e(6545).then(n.bind(n,8996)),"@site/versioned_docs/version-9.x/optional-components/mails.md",8996],"70b24a51":[()=>n.e(5808).then(n.t.bind(n,5608,19)),"~docs/default/tag-docs-next-tags-helper-467.json",5608],"738bceb6":[()=>n.e(6439).then(n.t.bind(n,80737,19)),"~docs/default/tag-docs-next-tags-notification-72b.json",80737],"7458314f":[()=>Promise.all([n.e(532),n.e(5619)]).then(n.bind(n,3598)),"@site/docs/components/main-components/index.md",3598],"75d21731":[()=>n.e(1757).then(n.bind(n,22260)),"@site/versioned_docs/version-11.x/main-components/exceptions.md",22260],76827885:[()=>n.e(6398).then(n.bind(n,67217)),"@site/versioned_docs/version-12.x/consulting.md",67217],"76f67042":[()=>n.e(178).then(n.bind(n,73008)),"@site/versioned_docs/version-11.x/optional-components/seeders.md",73008],"7768a617":[()=>n.e(9964).then(n.bind(n,79723)),"@site/versioned_docs/version-9.x/core-features/pagination.md",79723],"77aa4d93":[()=>n.e(4052).then(n.t.bind(n,33780,19)),"~docs/default/tag-docs-next-tags-container-fca.json",33780],"77c0bb11":[()=>n.e(3727).then(n.bind(n,51353)),"@site/versioned_docs/version-12.x/framework-features/rbac.md",51353],"7b0d43d2":[()=>n.e(5082).then(n.bind(n,90551)),"@site/versioned_docs/version-10.x/miscellaneous/tasks-scheduling.md",90551],"7b4e6d79":[()=>n.e(875).then(n.bind(n,90121)),"@site/versioned_docs/version-12.x/pacakges/localization.md",90121],"7c142331":[()=>n.e(5482).then(n.bind(n,93105)),"@site/docs/components/optional-components/policies.md",93105],"7c71f28e":[()=>n.e(2692).then(n.bind(n,8661)),"@site/versioned_docs/version-10.x/additional-features/community-containers/overview.md",8661],"7c87e584":[()=>n.e(2510).then(n.bind(n,71642)),"@site/versioned_docs/version-9.x/core-features/user-registration.md",71642],"7c884429":[()=>n.e(5790).then(n.t.bind(n,84933,19)),"~docs/default/tag-docs-next-tags-route-f65.json",84933],"7ced6537":[()=>n.e(8174).then(n.bind(n,74851)),"@site/docs/prologue/contribution-guide.md",74851],"7d1f259c":[()=>n.e(4492).then(n.bind(n,16501)),"@site/versioned_docs/version-11.x/getting-started/software-architectural-patterns.md",16501],"7dbacb84":[()=>n.e(8458).then(n.t.bind(n,65194,19)),"~docs/default/tag-docs-tags-route-7e4.json",65194],"8013ae06":[()=>n.e(4773).then(n.bind(n,30421)),"@site/versioned_docs/version-10.x/main-components/exceptions.md",30421],"819f8db2":[()=>n.e(2633).then(n.bind(n,41945)),"@site/versioned_docs/version-11.x/core-features/query-parameters.md",41945],"826b2a47":[()=>n.e(8404).then(n.bind(n,37349)),"@site/versioned_docs/version-12.x/components/main-components/tasks.md",37349],"82d76014":[()=>n.e(6493).then(n.t.bind(n,66989,19)),"~docs/default/tag-docs-next-tags-service-provider-e84.json",66989],"834e3df2":[()=>n.e(86).then(n.bind(n,68714)),"@site/docs/framework-features/rate-limiting.md",68714],"8375e767":[()=>n.e(8375).then(n.bind(n,64964)),"@site/versioned_docs/version-9.x/main-components/tasks.md",64964],84164204:[()=>n.e(260).then(n.t.bind(n,27598,19)),"~docs/default/tag-docs-tags-optional-component-3fe.json",27598],"86b21c86":[()=>n.e(2910).then(n.bind(n,12852)),"@site/versioned_docs/version-9.x/core-features/data-caching.md",12852],"87d519de":[()=>n.e(5189).then(n.bind(n,25615)),"@site/versioned_docs/version-10.x/main-components/models.md",25615],"881a9bc1":[()=>n.e(320).then(n.t.bind(n,63863,19)),"~docs/default/tag-docs-next-tags-main-component-c6b.json",63863],"8882a989":[()=>n.e(7692).then(n.t.bind(n,98498,19)),"~docs/default/tag-docs-next-tags-middleware-3e0.json",98498],"892cf132":[()=>n.e(2373).then(n.t.bind(n,5951,19)),"~docs/default/tag-docs-tags-lifecycle-d83.json",5951],"89918c0c":[()=>n.e(477).then(n.bind(n,38890)),"@site/versioned_docs/version-12.x/components/optional-components/commands.md",38890],"8b4b94e0":[()=>n.e(5690).then(n.bind(n,40543)),"@site/versioned_docs/version-12.x/components/main-components/exceptions.md",40543],"8c90118e":[()=>n.e(8378).then(n.bind(n,82310)),"@site/versioned_docs/version-9.x/core-features/etag.md",82310],"8c9fccb5":[()=>n.e(8995).then(n.bind(n,37602)),"@site/docs/components/optional-components/repository/repositories.md",37602],"8dd1624d":[()=>n.e(8762).then(n.bind(n,550)),"@site/docs/components/optional-components/commands.md",550],"8e0e9e2b":[()=>n.e(2238).then(n.bind(n,60130)),"@site/versioned_docs/version-10.x/miscellaneous/tests-helpers.md",60130],"8e61a622":[()=>n.e(2818).then(n.bind(n,31457)),"@site/versioned_docs/version-12.x/components/optional-components/repository/repositories.md",31457],"8ecb0387":[()=>Promise.all([n.e(532),n.e(8370)]).then(n.bind(n,76582)),"@site/versioned_docs/version-12.x/components/main-components/index.md",76582],"8f47f31d":[()=>n.e(3972).then(n.bind(n,73419)),"@site/versioned_docs/version-11.x/core-features/useful-commands.md",73419],"8fce0176":[()=>n.e(7572).then(n.bind(n,85001)),"@site/docs/components/main-components/controllers.md",85001],"90c91afe":[()=>n.e(4116).then(n.t.bind(n,57959,19)),"~docs/default/version-9-x-metadata-prop-0ad.json",57959],"92611ec4":[()=>n.e(9938).then(n.bind(n,34316)),"@site/versioned_docs/version-10.x/main-components/views.md",34316],"92b2ca3c":[()=>n.e(5108).then(n.bind(n,40050)),"@site/versioned_docs/version-9.x/core-features/validation.md",40050],"92ca3f12":[()=>n.e(3370).then(n.t.bind(n,29041,19)),"~docs/default/tag-docs-next-tags-exception-63f.json",29041],"92ea068e":[()=>n.e(5344).then(n.bind(n,86027)),"@site/versioned_docs/version-12.x/pacakges/readme.md",86027],"934030ed":[()=>n.e(439).then(n.bind(n,74029)),"@site/versioned_docs/version-9.x/miscellaneous/container-installer.md",74029],"935f2afb":[()=>n.e(53).then(n.t.bind(n,1109,19)),"~docs/default/version-current-metadata-prop-751.json",1109],"93746dcf":[()=>n.e(3468).then(n.bind(n,53607)),"@site/versioned_docs/version-12.x/framework-features/api-versioning.md",53607],"93d36f00":[()=>n.e(8317).then(n.bind(n,65105)),"@site/versioned_docs/version-9.x/optional-components/commands.md",65105],94009283:[()=>n.e(2517).then(n.bind(n,14970)),"@site/versioned_docs/version-10.x/core-features/api-versioning.md",14970],94056865:[()=>n.e(6007).then(n.bind(n,85376)),"@site/docs/components/optional-components/jobs.md",85376],"9465b6bc":[()=>n.e(255).then(n.t.bind(n,82309,19)),"~docs/default/tag-docs-next-tags-optional-component-5d5.json",82309],"9522a145":[()=>n.e(1555).then(n.bind(n,63497)),"@site/versioned_docs/version-12.x/components/optional-components/events.md",63497],"956e9f49":[()=>n.e(7104).then(n.bind(n,70957)),"@site/versioned_docs/version-9.x/optional-components/exceptions.md",70957],"959be4b4":[()=>n.e(7265).then(n.t.bind(n,22222,19)),"~docs/default/tag-docs-next-tags-criteria-3a3.json",22222],"963bb5ab":[()=>n.e(842).then(n.t.bind(n,98411,19)),"~docs/default/tag-docs-next-tags-queue-6b3.json",98411],"9650c219":[()=>n.e(8414).then(n.t.bind(n,70143,19)),"~docs/default/version-11-x-metadata-prop-c33.json",70143],"96944d2a":[()=>n.e(6733).then(n.bind(n,41678)),"@site/versioned_docs/version-10.x/miscellaneous/tasks-queuing.md",41678],"9840276f":[()=>n.e(8528).then(n.bind(n,19698)),"@site/versioned_docs/version-11.x/core-features/code-generator.md",19698],"986c3199":[()=>n.e(1090).then(n.t.bind(n,95492,19)),"~docs/default/tag-docs-tags-job-42d.json",95492],"98999a3e":[()=>n.e(8517).then(n.bind(n,75598)),"@site/versioned_docs/version-10.x/optional-components/providers.md",75598],"9944c6d1":[()=>n.e(6022).then(n.t.bind(n,24287,19)),"~docs/default/tag-docs-tags-response-461.json",24287],"99dcebb4":[()=>n.e(779).then(n.t.bind(n,28709,19)),"~docs/default/tag-docs-tags-seeder-940.json",28709],"99ff9a84":[()=>n.e(3046).then(n.bind(n,19096)),"@site/versioned_docs/version-11.x/core-features/authentication.md",19096],"9ba65a7f":[()=>n.e(8963).then(n.bind(n,75691)),"@site/docs/components/optional-components/tests.md",75691],"9bbc65ac":[()=>n.e(7210).then(n.t.bind(n,51020,19)),"~docs/default/tag-docs-tags-test-8ab.json",51020],"9c612400":[()=>n.e(4068).then(n.bind(n,81398)),"@site/versioned_docs/version-12.x/prologue/release-notes.md",81398],"9c985f15":[()=>n.e(2418).then(n.bind(n,14323)),"@site/versioned_docs/version-9.x/faq.md",14323],"9cc140f3":[()=>n.e(5049).then(n.t.bind(n,10429,19)),"~docs/default/tag-docs-tags-repository-b8c.json",10429],"9cd87160":[()=>n.e(1767).then(n.bind(n,3428)),"@site/versioned_docs/version-12.x/security/registration.mdx",3428],"9d6a5804":[()=>n.e(967).then(n.t.bind(n,54664,19)),"~docs/default/tag-docs-tags-mail-d65.json",54664],"9dd4b0ef":[()=>n.e(3592).then(n.bind(n,47436)),"@site/docs/framework-features/etag.md",47436],"9e0bb3f7":[()=>n.e(5322).then(n.t.bind(n,99203,19)),"~docs/default/tag-docs-tags-config-780.json",99203],"9e660ab4":[()=>n.e(4190).then(n.bind(n,63894)),"@site/versioned_docs/version-12.x/components/optional-components/values.md",63894],"9ec0fefb":[()=>n.e(4315).then(n.bind(n,17494)),"@site/versioned_docs/version-9.x/main-components/transporters.md",17494],"9f7214f0":[()=>n.e(3827).then(n.bind(n,10717)),"@site/versioned_docs/version-12.x/security/email-varification.md",10717],"9f93909f":[()=>n.e(6891).then(n.bind(n,4898)),"@site/docs/components/optional-components/service-providers.md",4898],"9fcd2aaf":[()=>n.e(2165).then(n.bind(n,84019)),"@site/versioned_docs/version-10.x/optional-components/factories.md",84019],a10ffd78:[()=>n.e(8098).then(n.t.bind(n,3378,19)),"~docs/default/tag-docs-tags-component-3a8.json",3378],a193a73b:[()=>n.e(3800).then(n.t.bind(n,15745,19)),"/home/runner/work/documentation/documentation/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",15745],a1a6e4a2:[()=>n.e(9001).then(n.bind(n,14697)),"@site/versioned_docs/version-12.x/components/main-components/models.md",14697],a247f8ec:[()=>n.e(9914).then(n.bind(n,49602)),"@site/docs/prologue/upgrade-guide.md",49602],a291d650:[()=>n.e(7513).then(n.bind(n,54591)),"@site/docs/architecture-concepts/readme.md",54591],a29806db:[()=>n.e(4365).then(n.bind(n,38552)),"@site/versioned_docs/version-9.x/getting-started/requirements.md",38552],a49f1700:[()=>n.e(5666).then(n.t.bind(n,41647,19)),"~docs/default/tag-docs-tags-rate-limiting-c85.json",41647],a4ce67ea:[()=>n.e(1945).then(n.bind(n,30630)),"@site/docs/prologue/release-notes.md",30630],a55de8d9:[()=>n.e(8473).then(n.bind(n,17645)),"@site/versioned_docs/version-9.x/optional-components/tests.md",17645],a63c68ec:[()=>n.e(9473).then(n.bind(n,62034)),"@site/versioned_docs/version-11.x/core-features/default-endpoints.md",62034],a71c172d:[()=>n.e(3624).then(n.bind(n,22465)),"@site/versioned_docs/version-10.x/getting-started/markdown-features.mdx",22465],a765b786:[()=>n.e(9646).then(n.bind(n,7901)),"@site/versioned_docs/version-11.x/faq.md",7901],a7d0887a:[()=>n.e(156).then(n.bind(n,57090)),"@site/docs/components/optional-components/helpers.md",57090],a8254ede:[()=>n.e(9419).then(n.bind(n,67692)),"@site/versioned_docs/version-9.x/main-components/controllers.md",67692],a8497d64:[()=>n.e(996).then(n.bind(n,43343)),"@site/versioned_docs/version-12.x/components/optional-components/middlewares.md",43343],a8bcf301:[()=>n.e(8862).then(n.bind(n,33659)),"@site/versioned_docs/version-10.x/core-features/authentication.md",33659],aa32d378:[()=>n.e(9977).then(n.bind(n,81233)),"@site/versioned_docs/version-9.x/optional-components/notifications.md",81233],aa3c5b40:[()=>n.e(2336).then(n.bind(n,20671)),"@site/versioned_docs/version-10.x/getting-started/responses.md",20671],aaf1cba5:[()=>Promise.all([n.e(532),n.e(312)]).then(n.bind(n,97826)),"@site/docs/framework-features/index.md",97826],abba3c1a:[()=>n.e(9206).then(n.bind(n,39821)),"@site/versioned_docs/version-11.x/getting-started/markdown-features.mdx",39821],abd7c6e1:[()=>n.e(2636).then(n.bind(n,61077)),"@site/versioned_docs/version-9.x/main-components/routes.md",61077],abe60d8e:[()=>n.e(9772).then(n.bind(n,25512)),"@site/versioned_docs/version-12.x/components/main-components/transformers.md",25512],aca4b983:[()=>n.e(8710).then(n.bind(n,44743)),"@site/versioned_docs/version-10.x/core-features/rate-limiting.md",44743],ace976d6:[()=>n.e(8924).then(n.bind(n,29410)),"@site/versioned_docs/version-9.x/optional-components/factories.md",29410],ad753fe5:[()=>n.e(2721).then(n.bind(n,45397)),"@site/docs/security/hash-id.md",45397],add102e2:[()=>n.e(7795).then(n.bind(n,62748)),"@site/docs/components/main-components/tasks.md",62748],af738211:[()=>n.e(3426).then(n.bind(n,59072)),"@site/versioned_docs/version-9.x/core-features/code-generator.md",59072],af8bcf34:[()=>n.e(9513).then(n.bind(n,49257)),"@site/versioned_docs/version-11.x/optional-components/migrations.md",49257],b1c5cbf2:[()=>n.e(8977).then(n.bind(n,54745)),"@site/versioned_docs/version-11.x/optional-components/mails.md",54745],b221159f:[()=>n.e(1660).then(n.t.bind(n,62410,19)),"~docs/default/tag-docs-tags-task-32d.json",62410],b2aa2b43:[()=>n.e(3445).then(n.bind(n,29406)),"@site/versioned_docs/version-10.x/getting-started/container-installer.md",29406],b2c1c78c:[()=>n.e(1738).then(n.bind(n,64745)),"@site/docs/components/main-components/views.md",64745],b3eefa80:[()=>n.e(4520).then(n.bind(n,17760)),"@site/docs/architecture-concepts/components.md",17760],b4ea3a21:[()=>n.e(6311).then(n.bind(n,6845)),"@site/versioned_docs/version-10.x/optional-components/notifications.md",6845],b50f730b:[()=>n.e(6690).then(n.bind(n,51574)),"@site/versioned_docs/version-9.x/main-components/views.md",51574],b5a0697e:[()=>n.e(2261).then(n.bind(n,78464)),"@site/versioned_docs/version-11.x/core-features/validation.md",78464],b5a6ea24:[()=>n.e(4020).then(n.bind(n,3964)),"@site/versioned_docs/version-9.x/core-features/api-versioning.md",3964],b626b9c9:[()=>n.e(4505).then(n.bind(n,5410)),"@site/versioned_docs/version-12.x/components/optional-components/repository/criterias.md",5410],b65ccb97:[()=>n.e(4269).then(n.t.bind(n,55092,19)),"~docs/default/version-12-x-metadata-prop-4c9.json",55092],b7b5f722:[()=>n.e(2964).then(n.bind(n,23456)),"@site/versioned_docs/version-10.x/core-features/user-registration.md",23456],b8242e5d:[()=>n.e(9712).then(n.t.bind(n,4879,19)),"~docs/default/tag-docs-next-tags-request-2c5.json",4879],b9d99630:[()=>n.e(778).then(n.bind(n,12491)),"@site/versioned_docs/version-11.x/getting-started/installation.md",12491],bab8e2ef:[()=>n.e(3934).then(n.bind(n,73280)),"@site/docs/components/main-components/actions.md",73280],bacc2700:[()=>n.e(383).then(n.bind(n,10918)),"@site/versioned_docs/version-11.x/optional-components/jobs.md",10918],bb0f6255:[()=>n.e(1949).then(n.bind(n,14795)),"@site/versioned_docs/version-11.x/main-components/subactions.md",14795],bb32ce55:[()=>Promise.all([n.e(532),n.e(7582)]).then(n.bind(n,82849)),"@site/versioned_docs/version-12.x/components/optional-components/index.md",82849],bd783ed9:[()=>Promise.all([n.e(532),n.e(7599)]).then(n.bind(n,8453)),"@site/docs/components/index.md",8453],bddd0f35:[()=>n.e(9631).then(n.bind(n,12866)),"@site/versioned_docs/version-12.x/architecture-concepts/porto.md",12866],bfdf68d4:[()=>n.e(6609).then(n.bind(n,61420)),"@site/versioned_docs/version-9.x/main-components/actions.md",61420],c02ab877:[()=>n.e(2421).then(n.bind(n,42987)),"@site/versioned_docs/version-10.x/getting-started/samples.md",42987],c16bfc7e:[()=>n.e(4521).then(n.t.bind(n,42541,19)),"~docs/default/tag-docs-next-tags-listener-524.json",42541],c1bf43f6:[()=>n.e(7362).then(n.bind(n,48852)),"@site/versioned_docs/version-9.x/optional-components/migrations.md",48852],c21f0827:[()=>n.e(4843).then(n.t.bind(n,56132,19)),"~docs/default/tag-docs-next-tags-lifecycle-873.json",56132],c2998ab0:[()=>n.e(1165).then(n.bind(n,42912)),"@site/versioned_docs/version-10.x/core-features/profiler.md",42912],c3ec0a52:[()=>n.e(6349).then(n.t.bind(n,59411,19)),"~docs/default/tag-docs-next-tags-controller-7bb.json",59411],c3fa5730:[()=>n.e(1403).then(n.bind(n,95478)),"@site/versioned_docs/version-11.x/getting-started/requirements.md",95478],c4023386:[()=>n.e(6614).then(n.bind(n,14332)),"@site/versioned_docs/version-10.x/additional-features/apiato-containers/payments.md",14332],c417dca4:[()=>n.e(7335).then(n.t.bind(n,50335,19)),"~docs/default/tag-docs-next-tags-policy-0bc.json",50335],c4cc7857:[()=>n.e(516).then(n.bind(n,42228)),"@site/docs/getting-started/customized-laravel-components.md",42228],c4f5d8e4:[()=>Promise.all([n.e(532),n.e(4314),n.e(4195)]).then(n.bind(n,33640)),"@site/src/pages/index.js",33640],c51d05bf:[()=>n.e(9753).then(n.bind(n,34698)),"@site/versioned_docs/version-10.x/core-features/useful-commands.md",34698],c6d7e964:[()=>n.e(688).then(n.bind(n,36233)),"@site/versioned_docs/version-9.x/miscellaneous/postman.md",36233],c7c4bc80:[()=>n.e(9198).then(n.t.bind(n,70808,19)),"~docs/default/tag-docs-tags-policy-cb3.json",70808],c8555b7b:[()=>n.e(79).then(n.bind(n,23351)),"@site/versioned_docs/version-11.x/additional-features/social-authentication.md",23351],c8795c05:[()=>n.e(5165).then(n.bind(n,75731)),"@site/versioned_docs/version-10.x/main-components/routes.md",75731],cb5f486b:[()=>n.e(951).then(n.t.bind(n,90707,19)),"~docs/default/version-10-x-metadata-prop-6fd.json",90707],cc5c6d64:[()=>n.e(8821).then(n.bind(n,11623)),"@site/versioned_docs/version-9.x/miscellaneous/magical-call.md",11623],cf3ee67c:[()=>n.e(869).then(n.t.bind(n,54997,19)),"~docs/default/tag-docs-next-tags-role-based-access-control-505.json",54997],cf7e6749:[()=>n.e(4793).then(n.bind(n,1098)),"@site/versioned_docs/version-11.x/additional-features/localization.md",1098],d055169c:[()=>n.e(8874).then(n.bind(n,40715)),"@site/versioned_docs/version-11.x/core-features/authorization.md",40715],d34bd87a:[()=>n.e(3825).then(n.bind(n,79623)),"@site/versioned_docs/version-9.x/main-components/models.md",79623],d3ed5dd0:[()=>n.e(4674).then(n.bind(n,4494)),"@site/versioned_docs/version-10.x/main-components/transformers.md",4494],d4124539:[()=>n.e(6957).then(n.bind(n,65739)),"@site/docs/components/main-components/exceptions.md",65739],d52025df:[()=>n.e(5964).then(n.bind(n,88215)),"@site/versioned_docs/version-9.x/contribution-guide.md",88215],d5f37c55:[()=>n.e(5079).then(n.bind(n,56039)),"@site/docs/framework-features/code-generator.md",56039],d6d55995:[()=>n.e(2769).then(n.bind(n,31247)),"@site/versioned_docs/version-10.x/core-features/default-endpoints.md",31247],d72ff326:[()=>n.e(5078).then(n.t.bind(n,77920,19)),"~docs/default/tag-docs-tags-exception-2a6.json",77920],d8325a2c:[()=>n.e(2970).then(n.bind(n,14621)),"@site/versioned_docs/version-9.x/optional-components/jobs.md",14621],d85472ef:[()=>n.e(6831).then(n.bind(n,62734)),"@site/versioned_docs/version-11.x/getting-started/container-installer.md",62734],d994236d:[()=>n.e(3756).then(n.bind(n,21539)),"@site/versioned_docs/version-12.x/pacakges/social-authentication.md",21539],da105351:[()=>n.e(2268).then(n.t.bind(n,30090,19)),"~docs/default/tag-docs-tags-event-d52.json",30090],da8b188d:[()=>n.e(8285).then(n.bind(n,60841)),"@site/versioned_docs/version-11.x/core-features/etag.md",60841],dae33245:[()=>n.e(8505).then(n.bind(n,89194)),"@site/versioned_docs/version-10.x/core-features/code-generator.md",89194],dae7336e:[()=>n.e(4545).then(n.t.bind(n,67753,19)),"~docs/default/tags-list-12-x-prop-1ac.json",67753],db1fb617:[()=>n.e(9226).then(n.bind(n,44969)),"@site/docs/architecture-concepts/porto.md",44969],db5219b9:[()=>n.e(7892).then(n.bind(n,51732)),"@site/docs/architecture-concepts/container.md",51732],db5d8c25:[()=>n.e(2379).then(n.bind(n,94)),"@site/versioned_docs/version-10.x/optional-components/commands.md",94],dbbc6b2c:[()=>n.e(8701).then(n.bind(n,25300)),"@site/versioned_docs/version-9.x/main-components/subactions.md",25300],dbdbfb09:[()=>n.e(8966).then(n.bind(n,72638)),"@site/versioned_docs/version-10.x/optional-components/events.md",72638],dbe067dd:[()=>n.e(728).then(n.bind(n,89563)),"@site/versioned_docs/version-9.x/core-features/localization.md",89563],ddec9574:[()=>n.e(8296).then(n.bind(n,30258)),"@site/versioned_docs/version-11.x/optional-components/configs.md",30258],de0ecdfa:[()=>n.e(8985).then(n.t.bind(n,34575,19)),"~docs/default/tag-docs-tags-transformer-bff.json",34575],deb44a68:[()=>n.e(4418).then(n.t.bind(n,59903,19)),"~docs/default/tag-docs-tags-sub-action-962.json",59903],df203c0f:[()=>n.e(9924).then(n.bind(n,97068)),"@theme/DocTagDocListPage",97068],df59c461:[()=>n.e(3209).then(n.bind(n,7064)),"@site/versioned_docs/version-9.x/core-features/search-query-parameter.md",7064],df6a485b:[()=>n.e(4978).then(n.t.bind(n,99129,19)),"~docs/default/tag-docs-tags-middleware-096.json",99129],df8fe5f1:[()=>n.e(1699).then(n.bind(n,43767)),"@site/versioned_docs/version-12.x/architecture-concepts/request-lifecycle.md",43767],e08582aa:[()=>n.e(3353).then(n.bind(n,56097)),"@site/versioned_docs/version-11.x/core-features/data-caching.md",56097],e1c5dcea:[()=>n.e(8385).then(n.bind(n,21777)),"@site/docs/pacakges/readme.md",21777],e5ac4d56:[()=>n.e(6694).then(n.t.bind(n,65055,19)),"~docs/default/tag-docs-tags-role-based-access-control-cba.json",65055],e5c09629:[()=>n.e(3344).then(n.bind(n,93447)),"@site/versioned_docs/version-11.x/core-features/api-versioning.md",93447],e63fc1b2:[()=>n.e(5293).then(n.bind(n,68581)),"@site/docs/components/main-components/transformers.md",68581],e848ef61:[()=>n.e(2328).then(n.t.bind(n,34914,19)),"~docs/default/tag-docs-tags-controller-21d.json",34914],e8ac06ec:[()=>n.e(843).then(n.bind(n,63244)),"@site/versioned_docs/version-9.x/core-features/authentication.md",63244],e8e0ef40:[()=>n.e(3771).then(n.bind(n,51456)),"@site/versioned_docs/version-10.x/optional-components/seeders.md",51456],e8f086b4:[()=>n.e(6462).then(n.bind(n,14526)),"@site/versioned_docs/version-12.x/security/hash-id.md",14526],eb3c6d3c:[()=>n.e(5970).then(n.t.bind(n,83833,19)),"~docs/default/tag-docs-next-tags-api-versioning-ad1.json",83833],ec13b678:[()=>n.e(4486).then(n.bind(n,4921)),"@site/versioned_docs/version-12.x/prologue/upgrade-guide.md",4921],ec989c0b:[()=>n.e(160).then(n.bind(n,55e3)),"@site/versioned_docs/version-11.x/main-components/views.md",55e3],ecd5baba:[()=>n.e(268).then(n.bind(n,7596)),"@site/versioned_docs/version-10.x/optional-components/languages.md",7596],ed111213:[()=>n.e(493).then(n.bind(n,71913)),"@site/versioned_docs/version-11.x/optional-components/middlewares.md",71913],eeb62e5b:[()=>n.e(5449).then(n.t.bind(n,53241,19)),"~docs/default/tag-docs-tags-framework-feature-95f.json",53241],ef04c7c8:[()=>n.e(9250).then(n.bind(n,7564)),"@site/docs/components/optional-components/middlewares.md",7564],ef35623d:[()=>n.e(508).then(n.bind(n,9031)),"@site/versioned_docs/version-9.x/miscellaneous/tasks-queuing.md",9031],ef449f35:[()=>n.e(7071).then(n.bind(n,58764)),"@site/versioned_docs/version-11.x/optional-components/criterias.md",58764],effa6628:[()=>n.e(9229).then(n.t.bind(n,61355,19)),"~docs/default/tag-docs-next-tags-command-840.json",61355],f0ea2bc1:[()=>n.e(1619).then(n.t.bind(n,77097,19)),"~docs/default/tag-docs-next-tags-porto-fd1.json",77097],f1121274:[()=>n.e(1017).then(n.bind(n,33305)),"@site/docs/components/optional-components/notifications.md",33305],f119515c:[()=>n.e(6862).then(n.bind(n,87392)),"@site/versioned_docs/version-10.x/main-components/tasks.md",87392],f187f30c:[()=>n.e(8952).then(n.bind(n,65237)),"@site/versioned_docs/version-9.x/core-features/default-endpoints.md",65237],f1acbeca:[()=>n.e(8002).then(n.bind(n,50761)),"@site/versioned_docs/version-10.x/optional-components/values.md",50761],f2b76d46:[()=>n.e(3302).then(n.bind(n,78523)),"@site/docs/framework-features/profiler.md",78523],f32194d4:[()=>n.e(9525).then(n.t.bind(n,75128,19)),"~docs/default/tag-docs-next-tags-architecture-951.json",75128],f3323e34:[()=>n.e(6284).then(n.t.bind(n,75726,19)),"~docs/default/tag-docs-tags-model-446.json",75726],f4ed0a4d:[()=>n.e(1818).then(n.bind(n,1571)),"@site/docs/pacakges/social-authentication.md",1571],f6807fb4:[()=>n.e(4788).then(n.t.bind(n,39836,19)),"~docs/default/tag-docs-tags-architecture-68d.json",39836],f7bd22f2:[()=>n.e(141).then(n.bind(n,4717)),"@site/docs/components/optional-components/values.md",4717],f981018f:[()=>n.e(8333).then(n.t.bind(n,20678,19)),"~docs/default/tag-docs-next-tags-view-064.json",20678],fa1390c3:[()=>n.e(1638).then(n.bind(n,49454)),"@site/versioned_docs/version-9.x/core-features/system-settings.md",49454],fba107f3:[()=>n.e(699).then(n.bind(n,17582)),"@site/versioned_docs/version-12.x/components/optional-components/helpers.md",17582],fbb5ccd8:[()=>n.e(5827).then(n.bind(n,54409)),"@site/versioned_docs/version-9.x/optional-components/configs.md",54409],fc7b1962:[()=>n.e(6973).then(n.bind(n,75677)),"@site/versioned_docs/version-9.x/optional-components/events.md",75677],fcdb2c3c:[()=>n.e(1698).then(n.bind(n,62735)),"@site/versioned_docs/version-9.x/optional-components/seeders.md",62735],fd1ff340:[()=>n.e(2831).then(n.t.bind(n,55011,19)),"~docs/default/tag-docs-next-tags-framework-feature-765.json",55011],fd3f6a3d:[()=>Promise.all([n.e(532),n.e(627)]).then(n.bind(n,96075)),"@site/versioned_docs/version-12.x/framework-features/index.md",96075],fdca9218:[()=>n.e(244).then(n.bind(n,46485)),"@site/versioned_docs/version-10.x/optional-components/configs.md",46485],fe4c7294:[()=>n.e(5176).then(n.t.bind(n,35359,19)),"~docs/default/tag-docs-next-tags-job-fd4.json",35359],ff4e10c4:[()=>n.e(798).then(n.bind(n,14554)),"@site/versioned_docs/version-11.x/main-components/routes.md",14554],ffafab28:[()=>Promise.all([n.e(532),n.e(8207)]).then(n.bind(n,86412)),"@site/versioned_docs/version-12.x/components/index.md",86412]};function l(e){let{error:t,retry:n,pastDelay:a}=e;return t?o.createElement("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"}},o.createElement("p",null,String(t)),o.createElement("div",null,o.createElement("button",{type:"button",onClick:n},"Retry"))):a?o.createElement("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"}},o.createElement("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb"},o.createElement("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2"},o.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},o.createElement("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),o.createElement("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),o.createElement("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),o.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},o.createElement("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),o.createElement("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),o.createElement("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),o.createElement("circle",{cx:"22",cy:"22",r:"8"},o.createElement("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"}))))):null}var d=n(99670),u=n(30226);function p(e,t){if("*"===e)return s()({loading:l,loader:()=>n.e(4972).then(n.bind(n,4972)),modules:["@theme/NotFound"],webpack:()=>[4972],render(e,t){const n=e.default;return o.createElement(u.z,{value:{plugin:{name:"native",id:"default"}}},o.createElement(n,t))}});const r=i[`${e}-${t}`],p={},m=[],f=[],g=(0,d.Z)(r);return Object.entries(g).forEach((e=>{let[t,n]=e;const o=c[n];o&&(p[t]=o[0],m.push(o[1]),f.push(o[2]))})),s().Map({loading:l,loader:p,modules:m,webpack:()=>f,render(t,n){const s=JSON.parse(JSON.stringify(r));Object.entries(t).forEach((t=>{let[n,o]=t;const a=o.default;if(!a)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof a&&"function"!=typeof a||Object.keys(o).filter((e=>"default"!==e)).forEach((e=>{a[e]=o[e]}));let r=s;const i=n.split(".");i.slice(0,-1).forEach((e=>{r=r[e]})),r[i[i.length-1]]=a}));const i=s.__comp;delete s.__comp;const c=s.__context;return delete s.__context,o.createElement(u.z,{value:c},o.createElement(i,(0,a.Z)({},s,n)))}})}const m=[{path:"/docs/next/tags",component:p("/docs/next/tags","bb6"),exact:!0},{path:"/docs/next/tags/action",component:p("/docs/next/tags/action","cd9"),exact:!0},{path:"/docs/next/tags/api-versioning",component:p("/docs/next/tags/api-versioning","aaa"),exact:!0},{path:"/docs/next/tags/architecture",component:p("/docs/next/tags/architecture","a55"),exact:!0},{path:"/docs/next/tags/authorization",component:p("/docs/next/tags/authorization","0a0"),exact:!0},{path:"/docs/next/tags/code-generator",component:p("/docs/next/tags/code-generator","1d7"),exact:!0},{path:"/docs/next/tags/command",component:p("/docs/next/tags/command","167"),exact:!0},{path:"/docs/next/tags/component",component:p("/docs/next/tags/component","e44"),exact:!0},{path:"/docs/next/tags/config",component:p("/docs/next/tags/config","6ca"),exact:!0},{path:"/docs/next/tags/container",component:p("/docs/next/tags/container","cb1"),exact:!0},{path:"/docs/next/tags/controller",component:p("/docs/next/tags/controller","c6f"),exact:!0},{path:"/docs/next/tags/criteria",component:p("/docs/next/tags/criteria","20b"),exact:!0},{path:"/docs/next/tags/etag",component:p("/docs/next/tags/etag","95c"),exact:!0},{path:"/docs/next/tags/event",component:p("/docs/next/tags/event","a7b"),exact:!0},{path:"/docs/next/tags/exception",component:p("/docs/next/tags/exception","5cf"),exact:!0},{path:"/docs/next/tags/factory",component:p("/docs/next/tags/factory","6d8"),exact:!0},{path:"/docs/next/tags/framework-feature",component:p("/docs/next/tags/framework-feature","4bf"),exact:!0},{path:"/docs/next/tags/helper",component:p("/docs/next/tags/helper","e2a"),exact:!0},{path:"/docs/next/tags/job",component:p("/docs/next/tags/job","e5e"),exact:!0},{path:"/docs/next/tags/lifecycle",component:p("/docs/next/tags/lifecycle","656"),exact:!0},{path:"/docs/next/tags/listener",component:p("/docs/next/tags/listener","9c4"),exact:!0},{path:"/docs/next/tags/mail",component:p("/docs/next/tags/mail","29c"),exact:!0},{path:"/docs/next/tags/main-component",component:p("/docs/next/tags/main-component","317"),exact:!0},{path:"/docs/next/tags/middleware",component:p("/docs/next/tags/middleware","840"),exact:!0},{path:"/docs/next/tags/migration",component:p("/docs/next/tags/migration","e45"),exact:!0},{path:"/docs/next/tags/model",component:p("/docs/next/tags/model","236"),exact:!0},{path:"/docs/next/tags/notification",component:p("/docs/next/tags/notification","692"),exact:!0},{path:"/docs/next/tags/optional-component",component:p("/docs/next/tags/optional-component","b85"),exact:!0},{path:"/docs/next/tags/policy",component:p("/docs/next/tags/policy","048"),exact:!0},{path:"/docs/next/tags/porto",component:p("/docs/next/tags/porto","a49"),exact:!0},{path:"/docs/next/tags/profiler",component:p("/docs/next/tags/profiler","50d"),exact:!0},{path:"/docs/next/tags/queue",component:p("/docs/next/tags/queue","aa8"),exact:!0},{path:"/docs/next/tags/rate-limiting",component:p("/docs/next/tags/rate-limiting","dc2"),exact:!0},{path:"/docs/next/tags/repository",component:p("/docs/next/tags/repository","f28"),exact:!0},{path:"/docs/next/tags/request",component:p("/docs/next/tags/request","ed1"),exact:!0},{path:"/docs/next/tags/response",component:p("/docs/next/tags/response","fa2"),exact:!0},{path:"/docs/next/tags/role-based-access-control",component:p("/docs/next/tags/role-based-access-control","8be"),exact:!0},{path:"/docs/next/tags/route",component:p("/docs/next/tags/route","b4a"),exact:!0},{path:"/docs/next/tags/seeder",component:p("/docs/next/tags/seeder","a3b"),exact:!0},{path:"/docs/next/tags/service-provider",component:p("/docs/next/tags/service-provider","ec2"),exact:!0},{path:"/docs/next/tags/sub-action",component:p("/docs/next/tags/sub-action","795"),exact:!0},{path:"/docs/next/tags/task",component:p("/docs/next/tags/task","0c1"),exact:!0},{path:"/docs/next/tags/test",component:p("/docs/next/tags/test","652"),exact:!0},{path:"/docs/next/tags/testing",component:p("/docs/next/tags/testing","8c4"),exact:!0},{path:"/docs/next/tags/transformer",component:p("/docs/next/tags/transformer","5e2"),exact:!0},{path:"/docs/next/tags/value",component:p("/docs/next/tags/value","763"),exact:!0},{path:"/docs/next/tags/view",component:p("/docs/next/tags/view","ad1"),exact:!0},{path:"/docs/tags",component:p("/docs/tags","304"),exact:!0},{path:"/docs/tags/action",component:p("/docs/tags/action","234"),exact:!0},{path:"/docs/tags/api-versioning",component:p("/docs/tags/api-versioning","2ef"),exact:!0},{path:"/docs/tags/architecture",component:p("/docs/tags/architecture","6e7"),exact:!0},{path:"/docs/tags/authorization",component:p("/docs/tags/authorization","606"),exact:!0},{path:"/docs/tags/code-generator",component:p("/docs/tags/code-generator","7aa"),exact:!0},{path:"/docs/tags/command",component:p("/docs/tags/command","7c7"),exact:!0},{path:"/docs/tags/component",component:p("/docs/tags/component","4ac"),exact:!0},{path:"/docs/tags/config",component:p("/docs/tags/config","e6a"),exact:!0},{path:"/docs/tags/container",component:p("/docs/tags/container","228"),exact:!0},{path:"/docs/tags/controller",component:p("/docs/tags/controller","537"),exact:!0},{path:"/docs/tags/criteria",component:p("/docs/tags/criteria","4fd"),exact:!0},{path:"/docs/tags/etag",component:p("/docs/tags/etag","d41"),exact:!0},{path:"/docs/tags/event",component:p("/docs/tags/event","f72"),exact:!0},{path:"/docs/tags/exception",component:p("/docs/tags/exception","27b"),exact:!0},{path:"/docs/tags/factory",component:p("/docs/tags/factory","724"),exact:!0},{path:"/docs/tags/framework-feature",component:p("/docs/tags/framework-feature","6ba"),exact:!0},{path:"/docs/tags/helper",component:p("/docs/tags/helper","452"),exact:!0},{path:"/docs/tags/job",component:p("/docs/tags/job","a4d"),exact:!0},{path:"/docs/tags/lifecycle",component:p("/docs/tags/lifecycle","19b"),exact:!0},{path:"/docs/tags/listener",component:p("/docs/tags/listener","dec"),exact:!0},{path:"/docs/tags/mail",component:p("/docs/tags/mail","d3f"),exact:!0},{path:"/docs/tags/main-component",component:p("/docs/tags/main-component","770"),exact:!0},{path:"/docs/tags/middleware",component:p("/docs/tags/middleware","fbc"),exact:!0},{path:"/docs/tags/migration",component:p("/docs/tags/migration","8ac"),exact:!0},{path:"/docs/tags/model",component:p("/docs/tags/model","bce"),exact:!0},{path:"/docs/tags/notification",component:p("/docs/tags/notification","980"),exact:!0},{path:"/docs/tags/optional-component",component:p("/docs/tags/optional-component","643"),exact:!0},{path:"/docs/tags/policy",component:p("/docs/tags/policy","21d"),exact:!0},{path:"/docs/tags/porto",component:p("/docs/tags/porto","6ce"),exact:!0},{path:"/docs/tags/profiler",component:p("/docs/tags/profiler","9c3"),exact:!0},{path:"/docs/tags/queue",component:p("/docs/tags/queue","43d"),exact:!0},{path:"/docs/tags/rate-limiting",component:p("/docs/tags/rate-limiting","c5d"),exact:!0},{path:"/docs/tags/repository",component:p("/docs/tags/repository","5d3"),exact:!0},{path:"/docs/tags/request",component:p("/docs/tags/request","28e"),exact:!0},{path:"/docs/tags/response",component:p("/docs/tags/response","177"),exact:!0},{path:"/docs/tags/role-based-access-control",component:p("/docs/tags/role-based-access-control","07e"),exact:!0},{path:"/docs/tags/route",component:p("/docs/tags/route","e35"),exact:!0},{path:"/docs/tags/seeder",component:p("/docs/tags/seeder","c9d"),exact:!0},{path:"/docs/tags/service-provider",component:p("/docs/tags/service-provider","d49"),exact:!0},{path:"/docs/tags/sub-action",component:p("/docs/tags/sub-action","e58"),exact:!0},{path:"/docs/tags/task",component:p("/docs/tags/task","9d8"),exact:!0},{path:"/docs/tags/test",component:p("/docs/tags/test","c0c"),exact:!0},{path:"/docs/tags/testing",component:p("/docs/tags/testing","665"),exact:!0},{path:"/docs/tags/transformer",component:p("/docs/tags/transformer","23d"),exact:!0},{path:"/docs/tags/value",component:p("/docs/tags/value","f01"),exact:!0},{path:"/docs/tags/view",component:p("/docs/tags/view","4ca"),exact:!0},{path:"/docs/10.x",component:p("/docs/10.x","fa0"),routes:[{path:"/docs/10.x/",component:p("/docs/10.x/","e9b"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/debugger",component:p("/docs/10.x/additional-features/apiato-containers/debugger","f01"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/documentation",component:p("/docs/10.x/additional-features/apiato-containers/documentation","798"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/localization",component:p("/docs/10.x/additional-features/apiato-containers/localization","465"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/overview",component:p("/docs/10.x/additional-features/apiato-containers/overview","75c"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/payments",component:p("/docs/10.x/additional-features/apiato-containers/payments","7a7"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/settings",component:p("/docs/10.x/additional-features/apiato-containers/settings","944"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/apiato-containers/social-authentication",component:p("/docs/10.x/additional-features/apiato-containers/social-authentication","032"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/additional-features/community-containers/overview",component:p("/docs/10.x/additional-features/community-containers/overview","962"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/contribution-guide",component:p("/docs/10.x/contribution-guide","520"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/api-versioning",component:p("/docs/10.x/core-features/api-versioning","477"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/authentication",component:p("/docs/10.x/core-features/authentication","1d4"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/authorization",component:p("/docs/10.x/core-features/authorization","77e"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/code-generator",component:p("/docs/10.x/core-features/code-generator","884"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/data-caching",component:p("/docs/10.x/core-features/data-caching","5fd"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/default-endpoints",component:p("/docs/10.x/core-features/default-endpoints","766"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/etag",component:p("/docs/10.x/core-features/etag","395"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/hash-id",component:p("/docs/10.x/core-features/hash-id","c06"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/pagination",component:p("/docs/10.x/core-features/pagination","cae"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/profiler",component:p("/docs/10.x/core-features/profiler","abc"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/query-parameters",component:p("/docs/10.x/core-features/query-parameters","6fa"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/rate-limiting",component:p("/docs/10.x/core-features/rate-limiting","272"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/useful-commands",component:p("/docs/10.x/core-features/useful-commands","a17"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/user-registration",component:p("/docs/10.x/core-features/user-registration","f1e"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/core-features/validation",component:p("/docs/10.x/core-features/validation","030"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/faq",component:p("/docs/10.x/faq","a94"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/container-installer",component:p("/docs/10.x/getting-started/container-installer","8dc"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/conventions-and-principles",component:p("/docs/10.x/getting-started/conventions-and-principles","7a0"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/installation",component:p("/docs/10.x/getting-started/installation","479"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/markdown-features",component:p("/docs/10.x/getting-started/markdown-features","cbf"),exact:!0},{path:"/docs/10.x/getting-started/requests",component:p("/docs/10.x/getting-started/requests","667"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/responses",component:p("/docs/10.x/getting-started/responses","28e"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/samples",component:p("/docs/10.x/getting-started/samples","488"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/getting-started/software-architectural-patterns",component:p("/docs/10.x/getting-started/software-architectural-patterns","316"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/actions",component:p("/docs/10.x/main-components/actions","509"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/controllers",component:p("/docs/10.x/main-components/controllers","959"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/exceptions",component:p("/docs/10.x/main-components/exceptions","9ba"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/models",component:p("/docs/10.x/main-components/models","9a2"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/requests",component:p("/docs/10.x/main-components/requests","ab9"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/routes",component:p("/docs/10.x/main-components/routes","bbf"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/subactions",component:p("/docs/10.x/main-components/subactions","127"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/tasks",component:p("/docs/10.x/main-components/tasks","cc6"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/transformers",component:p("/docs/10.x/main-components/transformers","45a"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/main-components/views",component:p("/docs/10.x/main-components/views","727"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/miscellaneous/tasks-queuing",component:p("/docs/10.x/miscellaneous/tasks-queuing","333"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/miscellaneous/tasks-scheduling",component:p("/docs/10.x/miscellaneous/tasks-scheduling","afc"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/miscellaneous/tests-helpers",component:p("/docs/10.x/miscellaneous/tests-helpers","916"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/commands",component:p("/docs/10.x/optional-components/commands","b38"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/configs",component:p("/docs/10.x/optional-components/configs","aac"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/criterias",component:p("/docs/10.x/optional-components/criterias","fb7"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/events",component:p("/docs/10.x/optional-components/events","2c1"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/factories",component:p("/docs/10.x/optional-components/factories","f8c"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/helpers",component:p("/docs/10.x/optional-components/helpers","4d0"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/jobs",component:p("/docs/10.x/optional-components/jobs","a8e"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/languages",component:p("/docs/10.x/optional-components/languages","4e8"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/mails",component:p("/docs/10.x/optional-components/mails","201"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/middlewares",component:p("/docs/10.x/optional-components/middlewares","68a"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/migrations",component:p("/docs/10.x/optional-components/migrations","df7"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/notifications",component:p("/docs/10.x/optional-components/notifications","66d"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/providers",component:p("/docs/10.x/optional-components/providers","1a6"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/repositories",component:p("/docs/10.x/optional-components/repositories","b88"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/seeders",component:p("/docs/10.x/optional-components/seeders","092"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/tests",component:p("/docs/10.x/optional-components/tests","0a2"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/optional-components/values",component:p("/docs/10.x/optional-components/values","39a"),exact:!0,sidebar:"version-10.x/docs"},{path:"/docs/10.x/upgrade-guide",component:p("/docs/10.x/upgrade-guide","b93"),exact:!0,sidebar:"version-10.x/docs"}]},{path:"/docs/11.x",component:p("/docs/11.x","637"),routes:[{path:"/docs/11.x/",component:p("/docs/11.x/","1d2"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/additional-features/documentation",component:p("/docs/11.x/additional-features/documentation","77d"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/additional-features/localization",component:p("/docs/11.x/additional-features/localization","422"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/additional-features/overview",component:p("/docs/11.x/additional-features/overview","adb"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/additional-features/social-authentication",component:p("/docs/11.x/additional-features/social-authentication","dd8"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/contribution-guide",component:p("/docs/11.x/contribution-guide","b58"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/api-versioning",component:p("/docs/11.x/core-features/api-versioning","4be"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/authentication",component:p("/docs/11.x/core-features/authentication","d6c"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/authorization",component:p("/docs/11.x/core-features/authorization","f18"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/code-generator",component:p("/docs/11.x/core-features/code-generator","624"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/data-caching",component:p("/docs/11.x/core-features/data-caching","3d3"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/default-endpoints",component:p("/docs/11.x/core-features/default-endpoints","310"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/etag",component:p("/docs/11.x/core-features/etag","0a9"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/hash-id",component:p("/docs/11.x/core-features/hash-id","4d5"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/pagination",component:p("/docs/11.x/core-features/pagination","c1a"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/profiler",component:p("/docs/11.x/core-features/profiler","209"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/query-parameters",component:p("/docs/11.x/core-features/query-parameters","dfa"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/rate-limiting",component:p("/docs/11.x/core-features/rate-limiting","d91"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/useful-commands",component:p("/docs/11.x/core-features/useful-commands","238"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/user-registration",component:p("/docs/11.x/core-features/user-registration","81f"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/core-features/validation",component:p("/docs/11.x/core-features/validation","de1"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/faq",component:p("/docs/11.x/faq","781"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/container-installer",component:p("/docs/11.x/getting-started/container-installer","237"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/conventions-and-principles",component:p("/docs/11.x/getting-started/conventions-and-principles","4a6"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/installation",component:p("/docs/11.x/getting-started/installation","965"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/markdown-features",component:p("/docs/11.x/getting-started/markdown-features","9bc"),exact:!0},{path:"/docs/11.x/getting-started/requests",component:p("/docs/11.x/getting-started/requests","644"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/responses",component:p("/docs/11.x/getting-started/responses","c7e"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/samples",component:p("/docs/11.x/getting-started/samples","bea"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/getting-started/software-architectural-patterns",component:p("/docs/11.x/getting-started/software-architectural-patterns","d34"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/actions",component:p("/docs/11.x/main-components/actions","2f5"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/controllers",component:p("/docs/11.x/main-components/controllers","48a"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/exceptions",component:p("/docs/11.x/main-components/exceptions","e6a"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/models",component:p("/docs/11.x/main-components/models","3de"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/requests",component:p("/docs/11.x/main-components/requests","a24"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/routes",component:p("/docs/11.x/main-components/routes","451"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/subactions",component:p("/docs/11.x/main-components/subactions","f75"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/tasks",component:p("/docs/11.x/main-components/tasks","012"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/transformers",component:p("/docs/11.x/main-components/transformers","510"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/main-components/views",component:p("/docs/11.x/main-components/views","2c2"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/miscellaneous/tasks-queuing",component:p("/docs/11.x/miscellaneous/tasks-queuing","be7"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/miscellaneous/tasks-scheduling",component:p("/docs/11.x/miscellaneous/tasks-scheduling","619"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/miscellaneous/tests-helpers",component:p("/docs/11.x/miscellaneous/tests-helpers","2ca"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/commands",component:p("/docs/11.x/optional-components/commands","44b"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/configs",component:p("/docs/11.x/optional-components/configs","b02"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/criterias",component:p("/docs/11.x/optional-components/criterias","a4f"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/events",component:p("/docs/11.x/optional-components/events","0a5"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/factories",component:p("/docs/11.x/optional-components/factories","4bc"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/helpers",component:p("/docs/11.x/optional-components/helpers","f9e"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/jobs",component:p("/docs/11.x/optional-components/jobs","10d"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/languages",component:p("/docs/11.x/optional-components/languages","c3f"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/mails",component:p("/docs/11.x/optional-components/mails","6b7"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/middlewares",component:p("/docs/11.x/optional-components/middlewares","645"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/migrations",component:p("/docs/11.x/optional-components/migrations","e24"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/notifications",component:p("/docs/11.x/optional-components/notifications","c87"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/providers",component:p("/docs/11.x/optional-components/providers","b4b"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/repositories",component:p("/docs/11.x/optional-components/repositories","49f"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/seeders",component:p("/docs/11.x/optional-components/seeders","7bc"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/tests",component:p("/docs/11.x/optional-components/tests","fa4"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/optional-components/values",component:p("/docs/11.x/optional-components/values","bf9"),exact:!0,sidebar:"version-11.x/docs"},{path:"/docs/11.x/upgrade-guide",component:p("/docs/11.x/upgrade-guide","da5"),exact:!0,sidebar:"version-11.x/docs"}]},{path:"/docs/9.x",component:p("/docs/9.x","5a9"),routes:[{path:"/docs/9.x/",component:p("/docs/9.x/","a00"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/contribution-guide",component:p("/docs/9.x/contribution-guide","b59"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/admin-dashboard",component:p("/docs/9.x/core-features/admin-dashboard","db2"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/api-docs-generator",component:p("/docs/9.x/core-features/api-docs-generator","9ee"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/api-versioning",component:p("/docs/9.x/core-features/api-versioning","e8f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/authentication",component:p("/docs/9.x/core-features/authentication","069"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/authorization",component:p("/docs/9.x/core-features/authorization","02c"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/code-generator",component:p("/docs/9.x/core-features/code-generator","a7e"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/data-caching",component:p("/docs/9.x/core-features/data-caching","e83"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/default-endpoints",component:p("/docs/9.x/core-features/default-endpoints","f22"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/etag",component:p("/docs/9.x/core-features/etag","4dc"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/hash-id",component:p("/docs/9.x/core-features/hash-id","32a"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/localization",component:p("/docs/9.x/core-features/localization","79e"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/pagination",component:p("/docs/9.x/core-features/pagination","da0"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/payments",component:p("/docs/9.x/core-features/payments","aaa"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/profiler",component:p("/docs/9.x/core-features/profiler","176"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/query-parameters",component:p("/docs/9.x/core-features/query-parameters","9e0"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/rate-limiting",component:p("/docs/9.x/core-features/rate-limiting","f27"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/requests-monitor",component:p("/docs/9.x/core-features/requests-monitor","34f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/search-query-parameter",component:p("/docs/9.x/core-features/search-query-parameter","ad5"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/social-authentication",component:p("/docs/9.x/core-features/social-authentication","2f4"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/system-settings",component:p("/docs/9.x/core-features/system-settings","62d"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/useful-commands",component:p("/docs/9.x/core-features/useful-commands","b73"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/user-registration",component:p("/docs/9.x/core-features/user-registration","6ca"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/core-features/validation",component:p("/docs/9.x/core-features/validation","1d2"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/faq",component:p("/docs/9.x/faq","521"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/conventions-and-principles",component:p("/docs/9.x/getting-started/conventions-and-principles","e36"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/installation",component:p("/docs/9.x/getting-started/installation","f35"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/markdown-features",component:p("/docs/9.x/getting-started/markdown-features","ada"),exact:!0},{path:"/docs/9.x/getting-started/overview",component:p("/docs/9.x/getting-started/overview","4cd"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/requests",component:p("/docs/9.x/getting-started/requests","f04"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/responses",component:p("/docs/9.x/getting-started/responses","798"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/getting-started/software-architectural-patterns",component:p("/docs/9.x/getting-started/software-architectural-patterns","ea5"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/actions",component:p("/docs/9.x/main-components/actions","9a5"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/controllers",component:p("/docs/9.x/main-components/controllers","e50"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/models",component:p("/docs/9.x/main-components/models","490"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/requests",component:p("/docs/9.x/main-components/requests","e0e"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/routes",component:p("/docs/9.x/main-components/routes","d0f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/subactions",component:p("/docs/9.x/main-components/subactions","928"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/tasks",component:p("/docs/9.x/main-components/tasks","b9d"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/transformers",component:p("/docs/9.x/main-components/transformers","676"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/transporters",component:p("/docs/9.x/main-components/transporters","686"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/main-components/views",component:p("/docs/9.x/main-components/views","851"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/container-installer",component:p("/docs/9.x/miscellaneous/container-installer","ebf"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/magical-call",component:p("/docs/9.x/miscellaneous/magical-call","098"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/postman",component:p("/docs/9.x/miscellaneous/postman","e98"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/tasks-queuing",component:p("/docs/9.x/miscellaneous/tasks-queuing","627"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/tasks-scheduling",component:p("/docs/9.x/miscellaneous/tasks-scheduling","661"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/miscellaneous/tests-helpers",component:p("/docs/9.x/miscellaneous/tests-helpers","634"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/commands",component:p("/docs/9.x/optional-components/commands","02f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/configs",component:p("/docs/9.x/optional-components/configs","ddf"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/criterias",component:p("/docs/9.x/optional-components/criterias","f53"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/events",component:p("/docs/9.x/optional-components/events","36a"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/exceptions",component:p("/docs/9.x/optional-components/exceptions","b7f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/factories",component:p("/docs/9.x/optional-components/factories","40b"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/jobs",component:p("/docs/9.x/optional-components/jobs","75c"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/languages",component:p("/docs/9.x/optional-components/languages","4fd"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/mails",component:p("/docs/9.x/optional-components/mails","b56"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/middlewares",component:p("/docs/9.x/optional-components/middlewares","bff"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/migrations",component:p("/docs/9.x/optional-components/migrations","f4c"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/notifications",component:p("/docs/9.x/optional-components/notifications","76f"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/providers",component:p("/docs/9.x/optional-components/providers","e2e"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/repositories",component:p("/docs/9.x/optional-components/repositories","a33"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/seeders",component:p("/docs/9.x/optional-components/seeders","875"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/tests",component:p("/docs/9.x/optional-components/tests","098"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/optional-components/values",component:p("/docs/9.x/optional-components/values","868"),exact:!0,sidebar:"version-9.x/docs"},{path:"/docs/9.x/upgrade-guide",component:p("/docs/9.x/upgrade-guide","a0d"),exact:!0,sidebar:"version-9.x/docs"}]},{path:"/docs/next",component:p("/docs/next","389"),routes:[{path:"/docs/next/architecture-concepts/",component:p("/docs/next/architecture-concepts/","d52"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/architecture-concepts/components",component:p("/docs/next/architecture-concepts/components","2cf"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/architecture-concepts/container",component:p("/docs/next/architecture-concepts/container","ed9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/architecture-concepts/porto",component:p("/docs/next/architecture-concepts/porto","5f5"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/architecture-concepts/request-lifecycle",component:p("/docs/next/architecture-concepts/request-lifecycle","2b8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/",component:p("/docs/next/components/","9ba"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/",component:p("/docs/next/components/main-components/","0f7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/actions",component:p("/docs/next/components/main-components/actions","9ce"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/controllers",component:p("/docs/next/components/main-components/controllers","e58"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/exceptions",component:p("/docs/next/components/main-components/exceptions","900"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/models",component:p("/docs/next/components/main-components/models","9b8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/requests",component:p("/docs/next/components/main-components/requests","8e3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/routes",component:p("/docs/next/components/main-components/routes","56b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/subactions",component:p("/docs/next/components/main-components/subactions","7da"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/tasks",component:p("/docs/next/components/main-components/tasks","567"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/transformers",component:p("/docs/next/components/main-components/transformers","9a7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/main-components/views",component:p("/docs/next/components/main-components/views","959"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/",component:p("/docs/next/components/optional-components/","649"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/commands",component:p("/docs/next/components/optional-components/commands","847"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/configs",component:p("/docs/next/components/optional-components/configs","35a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/events",component:p("/docs/next/components/optional-components/events","43d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/factories",component:p("/docs/next/components/optional-components/factories","a4f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/helpers",component:p("/docs/next/components/optional-components/helpers","7ea"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/jobs",component:p("/docs/next/components/optional-components/jobs","db6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/mail",component:p("/docs/next/components/optional-components/mail","209"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/middlewares",component:p("/docs/next/components/optional-components/middlewares","a64"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/migrations",component:p("/docs/next/components/optional-components/migrations","c3c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/notifications",component:p("/docs/next/components/optional-components/notifications","602"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/policies",component:p("/docs/next/components/optional-components/policies","c33"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/repository/criterias",component:p("/docs/next/components/optional-components/repository/criterias","894"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/repository/repositories",component:p("/docs/next/components/optional-components/repository/repositories","4ae"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/seeders",component:p("/docs/next/components/optional-components/seeders","e88"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/service-providers",component:p("/docs/next/components/optional-components/service-providers","003"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/tests",component:p("/docs/next/components/optional-components/tests","fe3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/components/optional-components/values",component:p("/docs/next/components/optional-components/values","71b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/consulting",component:p("/docs/next/consulting","20e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/",component:p("/docs/next/framework-features/","503"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/api-versioning",component:p("/docs/next/framework-features/api-versioning","699"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/code-generator",component:p("/docs/next/framework-features/code-generator","97a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/etag",component:p("/docs/next/framework-features/etag","f10"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/profiler",component:p("/docs/next/framework-features/profiler","17c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/rate-limiting",component:p("/docs/next/framework-features/rate-limiting","53e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/framework-features/rbac",component:p("/docs/next/framework-features/rbac","e0a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/getting-started/best-practices",component:p("/docs/next/getting-started/best-practices","928"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/getting-started/customized-laravel-components",component:p("/docs/next/getting-started/customized-laravel-components","df4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/getting-started/installation",component:p("/docs/next/getting-started/installation","38e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/pacakges/",component:p("/docs/next/pacakges/","cd9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/pacakges/documentation",component:p("/docs/next/pacakges/documentation","601"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/pacakges/localization",component:p("/docs/next/pacakges/localization","2b4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/pacakges/social-authentication",component:p("/docs/next/pacakges/social-authentication","d10"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/prologue/contribution-guide",component:p("/docs/next/prologue/contribution-guide","ad2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/prologue/release-notes",component:p("/docs/next/prologue/release-notes","cf6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/prologue/upgrade-guide",component:p("/docs/next/prologue/upgrade-guide","6f8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/authentication",component:p("/docs/next/security/authentication","142"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/authorization",component:p("/docs/next/security/authorization","313"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/email-varification",component:p("/docs/next/security/email-varification","dc5"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/hash-id",component:p("/docs/next/security/hash-id","a44"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/password-reset",component:p("/docs/next/security/password-reset","ba3"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/next/security/registration",component:p("/docs/next/security/registration","535"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/docs",component:p("/docs","2f0"),routes:[{path:"/docs/architecture-concepts/",component:p("/docs/architecture-concepts/","13d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/architecture-concepts/components",component:p("/docs/architecture-concepts/components","ba1"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/architecture-concepts/container",component:p("/docs/architecture-concepts/container","032"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/architecture-concepts/porto",component:p("/docs/architecture-concepts/porto","ada"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/architecture-concepts/request-lifecycle",component:p("/docs/architecture-concepts/request-lifecycle","7a0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/",component:p("/docs/components/","329"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/",component:p("/docs/components/main-components/","ac2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/actions",component:p("/docs/components/main-components/actions","12c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/controllers",component:p("/docs/components/main-components/controllers","74a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/exceptions",component:p("/docs/components/main-components/exceptions","4ba"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/models",component:p("/docs/components/main-components/models","0cd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/requests",component:p("/docs/components/main-components/requests","683"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/routes",component:p("/docs/components/main-components/routes","2f7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/subactions",component:p("/docs/components/main-components/subactions","11b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/tasks",component:p("/docs/components/main-components/tasks","171"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/transformers",component:p("/docs/components/main-components/transformers","3ff"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/main-components/views",component:p("/docs/components/main-components/views","e88"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/",component:p("/docs/components/optional-components/","99e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/commands",component:p("/docs/components/optional-components/commands","5e4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/configs",component:p("/docs/components/optional-components/configs","430"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/events",component:p("/docs/components/optional-components/events","475"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/factories",component:p("/docs/components/optional-components/factories","439"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/helpers",component:p("/docs/components/optional-components/helpers","992"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/jobs",component:p("/docs/components/optional-components/jobs","6b7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/mail",component:p("/docs/components/optional-components/mail","0c2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/middlewares",component:p("/docs/components/optional-components/middlewares","7ee"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/migrations",component:p("/docs/components/optional-components/migrations","0fb"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/notifications",component:p("/docs/components/optional-components/notifications","8d5"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/policies",component:p("/docs/components/optional-components/policies","d6f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/repository/criterias",component:p("/docs/components/optional-components/repository/criterias","d79"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/repository/repositories",component:p("/docs/components/optional-components/repository/repositories","897"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/seeders",component:p("/docs/components/optional-components/seeders","167"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/service-providers",component:p("/docs/components/optional-components/service-providers","fb7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/tests",component:p("/docs/components/optional-components/tests","88c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/components/optional-components/values",component:p("/docs/components/optional-components/values","43e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/consulting",component:p("/docs/consulting","4e6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/",component:p("/docs/framework-features/","886"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/api-versioning",component:p("/docs/framework-features/api-versioning","1f6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/code-generator",component:p("/docs/framework-features/code-generator","168"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/etag",component:p("/docs/framework-features/etag","082"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/profiler",component:p("/docs/framework-features/profiler","151"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/rate-limiting",component:p("/docs/framework-features/rate-limiting","c25"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/framework-features/rbac",component:p("/docs/framework-features/rbac","8ec"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/getting-started/best-practices",component:p("/docs/getting-started/best-practices","1f0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/getting-started/customized-laravel-components",component:p("/docs/getting-started/customized-laravel-components","422"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/getting-started/installation",component:p("/docs/getting-started/installation","872"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/pacakges/",component:p("/docs/pacakges/","97c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/pacakges/documentation",component:p("/docs/pacakges/documentation","6fc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/pacakges/localization",component:p("/docs/pacakges/localization","838"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/pacakges/social-authentication",component:p("/docs/pacakges/social-authentication","49a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/prologue/contribution-guide",component:p("/docs/prologue/contribution-guide","eb2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/prologue/release-notes",component:p("/docs/prologue/release-notes","232"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/prologue/upgrade-guide",component:p("/docs/prologue/upgrade-guide","581"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/authentication",component:p("/docs/security/authentication","80f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/authorization",component:p("/docs/security/authorization","280"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/email-varification",component:p("/docs/security/email-varification","f96"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/hash-id",component:p("/docs/security/hash-id","e6e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/password-reset",component:p("/docs/security/password-reset","065"),exact:!0,sidebar:"tutorialSidebar"},{path:"/docs/security/registration",component:p("/docs/security/registration","06a"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/",component:p("/","379"),exact:!0},{path:"*",component:p("*")}]},98934:(e,t,n)=>{"use strict";n.d(t,{_:()=>a,t:()=>r});var o=n(67294);const a=o.createContext(!1);function r(e){let{children:t}=e;const[n,r]=(0,o.useState)(!1);return(0,o.useEffect)((()=>{r(!0)}),[]),o.createElement(a.Provider,{value:n},t)}},97221:(e,t,n)=>{"use strict";var o=n(67294),a=n(73935),r=n(73727),s=n(70405),i=n(10412);const c=[n(32497),n(3310),n(18320),n(52295)];var l=n(723),d=n(16550),u=n(18790);function p(e){let{children:t}=e;return o.createElement(o.Fragment,null,t)}var m=n(87462),f=n(35742),g=n(52263),h=n(44996),b=n(86668),v=n(1944),x=n(94711),y=n(19727),_=n(43320),w=n(18780),k=n(90197);function S(){const{i18n:{defaultLocale:e,localeConfigs:t}}=(0,g.Z)(),n=(0,x.l)();return o.createElement(f.Z,null,Object.entries(t).map((e=>{let[t,{htmlLang:a}]=e;return o.createElement("link",{key:t,rel:"alternate",href:n.createUrl({locale:t,fullyQualified:!0}),hrefLang:a})})),o.createElement("link",{rel:"alternate",href:n.createUrl({locale:e,fullyQualified:!0}),hrefLang:"x-default"}))}function E(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,g.Z)(),a=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,g.Z)(),{pathname:o}=(0,d.TH)();return e+(0,w.applyTrailingSlash)((0,h.Z)(o),{trailingSlash:n,baseUrl:t})}(),r=t?`${n}${t}`:a;return o.createElement(f.Z,null,o.createElement("meta",{property:"og:url",content:r}),o.createElement("link",{rel:"canonical",href:r}))}function T(){const{i18n:{currentLocale:e}}=(0,g.Z)(),{metadata:t,image:n}=(0,b.L)();return o.createElement(o.Fragment,null,o.createElement(f.Z,null,o.createElement("meta",{name:"twitter:card",content:"summary_large_image"}),o.createElement("body",{className:y.h})),n&&o.createElement(v.d,{image:n}),o.createElement(E,null),o.createElement(S,null),o.createElement(k.Z,{tag:_.HX,locale:e}),o.createElement(f.Z,null,t.map(((e,t)=>o.createElement("meta",(0,m.Z)({key:t},e))))))}const C=new Map;function A(e){if(C.has(e.pathname))return{...e,pathname:C.get(e.pathname)};if((0,u.f)(l.Z,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return C.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return C.set(e.pathname,t),{...e,pathname:t}}var L=n(98934),O=n(58940);function N(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o{const o=t.default?.[e]??t[e];return o?.(...n)}));return()=>a.forEach((e=>e?.()))}const P=function(e){let{children:t,location:n,previousLocation:a}=e;return(0,o.useLayoutEffect)((()=>{a!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const o=t.pathname===n.pathname,a=t.hash===n.hash,r=t.search===n.search;if(o&&a&&!r)return;const{hash:s}=t;if(s){const e=decodeURIComponent(s.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:a}),N("onRouteDidUpdate",{previousLocation:a,location:n}))}),[a,n]),t};function I(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,u.f)(l.Z,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class R extends o.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=i.Z.canUseDOM?N("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=N("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),I(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return o.createElement(P,{previousLocation:this.previousLocation,location:t},o.createElement(d.AW,{location:t,render:()=>e}))}}const M=R,D="__docusaurus-base-url-issue-banner-container",B="__docusaurus-base-url-issue-banner",j="__docusaurus-base-url-issue-banner-suggestion-container",F="__DOCUSAURUS_INSERT_BASEURL_BANNER";function z(e){return`\nwindow['${F}'] = true;\n\ndocument.addEventListener('DOMContentLoaded', maybeInsertBanner);\n\nfunction maybeInsertBanner() {\n var shouldInsert = window['${F}'];\n shouldInsert && insertBanner();\n}\n\nfunction insertBanner() {\n var bannerContainer = document.getElementById('${D}');\n if (!bannerContainer) {\n return;\n }\n var bannerHtml = ${JSON.stringify(function(e){return`\n
\n

Your Docusaurus site did not load properly.

\n

A very common reason is a wrong site baseUrl configuration.

\n

Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}

\n

We suggest trying baseUrl =

\n
\n`}(e)).replace(/{window[F]=!1}),[]),o.createElement(o.Fragment,null,!i.Z.canUseDOM&&o.createElement(f.Z,null,o.createElement("script",null,z(e))),o.createElement("div",{id:D}))}function U(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,g.Z)(),{pathname:n}=(0,d.TH)();return t&&n===e?o.createElement($,null):null}function q(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:a,localeConfigs:r}}=(0,g.Z)(),s=(0,h.Z)(e),{htmlLang:i,direction:c}=r[a];return o.createElement(f.Z,null,o.createElement("html",{lang:i,dir:c}),o.createElement("title",null,t),o.createElement("meta",{property:"og:title",content:t}),o.createElement("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&o.createElement("meta",{name:"robots",content:"noindex, nofollow"}),e&&o.createElement("link",{rel:"icon",href:s}))}var H=n(44763),Z=n(72389);function V(){const e=(0,Z.Z)();return o.createElement(f.Z,null,o.createElement("html",{"data-has-hydrated":e}))}function G(){const e=(0,u.H)(l.Z),t=(0,d.TH)();return o.createElement(H.Z,null,o.createElement(O.M,null,o.createElement(L.t,null,o.createElement(p,null,o.createElement(q,null),o.createElement(T,null),o.createElement(U,null),o.createElement(M,{location:A(t)},e)),o.createElement(V,null))))}var W=n(16887);const Y=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{if("undefined"==typeof document)return void n();const o=document.createElement("link");o.setAttribute("rel","prefetch"),o.setAttribute("href",e),o.onload=()=>t(),o.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(o)}))}:function(e){return new Promise(((t,n)=>{const o=new XMLHttpRequest;o.open("GET",e,!0),o.withCredentials=!0,o.onload=()=>{200===o.status?t():n()},o.send(null)}))};var K=n(99670);const X=new Set,Q=new Set,J=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,ee={prefetch(e){if(!(e=>!J()&&!Q.has(e)&&!X.has(e))(e))return!1;X.add(e);const t=(0,u.f)(l.Z,e).flatMap((e=>{return t=e.route.path,Object.entries(W).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,K.Z)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?Y(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!J()&&!Q.has(e))(e)&&(Q.add(e),I(e))},te=Object.freeze(ee);if(i.Z.canUseDOM){window.docusaurus=te;const e=a.hydrate;I(window.location.pathname).then((()=>{e(o.createElement(s.B6,null,o.createElement(r.VK,null,o.createElement(G,null))),document.getElementById("__docusaurus"))}))}},58940:(e,t,n)=>{"use strict";n.d(t,{_:()=>d,M:()=>u});var o=n(67294),a=n(36809);const r=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/docs","versions":[{"name":"current","label":"Next \ud83d\udea7","isLast":false,"path":"/docs/next","mainDocId":"prologue/release-notes","docs":[{"id":"architecture-concepts/components","path":"/docs/next/architecture-concepts/components","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/container","path":"/docs/next/architecture-concepts/container","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/porto","path":"/docs/next/architecture-concepts/porto","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/readme","path":"/docs/next/architecture-concepts/","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/request-lifecycle","path":"/docs/next/architecture-concepts/request-lifecycle","sidebar":"tutorialSidebar"},{"id":"components/index","path":"/docs/next/components/","sidebar":"tutorialSidebar"},{"id":"components/main-components/actions","path":"/docs/next/components/main-components/actions","sidebar":"tutorialSidebar"},{"id":"components/main-components/controllers","path":"/docs/next/components/main-components/controllers","sidebar":"tutorialSidebar"},{"id":"components/main-components/exceptions","path":"/docs/next/components/main-components/exceptions","sidebar":"tutorialSidebar"},{"id":"components/main-components/index","path":"/docs/next/components/main-components/","sidebar":"tutorialSidebar"},{"id":"components/main-components/models","path":"/docs/next/components/main-components/models","sidebar":"tutorialSidebar"},{"id":"components/main-components/requests","path":"/docs/next/components/main-components/requests","sidebar":"tutorialSidebar"},{"id":"components/main-components/routes","path":"/docs/next/components/main-components/routes","sidebar":"tutorialSidebar"},{"id":"components/main-components/subactions","path":"/docs/next/components/main-components/subactions","sidebar":"tutorialSidebar"},{"id":"components/main-components/tasks","path":"/docs/next/components/main-components/tasks","sidebar":"tutorialSidebar"},{"id":"components/main-components/transformers","path":"/docs/next/components/main-components/transformers","sidebar":"tutorialSidebar"},{"id":"components/main-components/views","path":"/docs/next/components/main-components/views","sidebar":"tutorialSidebar"},{"id":"components/optional-components/commands","path":"/docs/next/components/optional-components/commands","sidebar":"tutorialSidebar"},{"id":"components/optional-components/configs","path":"/docs/next/components/optional-components/configs","sidebar":"tutorialSidebar"},{"id":"components/optional-components/events","path":"/docs/next/components/optional-components/events","sidebar":"tutorialSidebar"},{"id":"components/optional-components/factories","path":"/docs/next/components/optional-components/factories","sidebar":"tutorialSidebar"},{"id":"components/optional-components/helpers","path":"/docs/next/components/optional-components/helpers","sidebar":"tutorialSidebar"},{"id":"components/optional-components/index","path":"/docs/next/components/optional-components/","sidebar":"tutorialSidebar"},{"id":"components/optional-components/jobs","path":"/docs/next/components/optional-components/jobs","sidebar":"tutorialSidebar"},{"id":"components/optional-components/mail","path":"/docs/next/components/optional-components/mail","sidebar":"tutorialSidebar"},{"id":"components/optional-components/middlewares","path":"/docs/next/components/optional-components/middlewares","sidebar":"tutorialSidebar"},{"id":"components/optional-components/migrations","path":"/docs/next/components/optional-components/migrations","sidebar":"tutorialSidebar"},{"id":"components/optional-components/notifications","path":"/docs/next/components/optional-components/notifications","sidebar":"tutorialSidebar"},{"id":"components/optional-components/policies","path":"/docs/next/components/optional-components/policies","sidebar":"tutorialSidebar"},{"id":"components/optional-components/repository/criterias","path":"/docs/next/components/optional-components/repository/criterias","sidebar":"tutorialSidebar"},{"id":"components/optional-components/repository/repositories","path":"/docs/next/components/optional-components/repository/repositories","sidebar":"tutorialSidebar"},{"id":"components/optional-components/seeders","path":"/docs/next/components/optional-components/seeders","sidebar":"tutorialSidebar"},{"id":"components/optional-components/service-providers","path":"/docs/next/components/optional-components/service-providers","sidebar":"tutorialSidebar"},{"id":"components/optional-components/tests","path":"/docs/next/components/optional-components/tests","sidebar":"tutorialSidebar"},{"id":"components/optional-components/values","path":"/docs/next/components/optional-components/values","sidebar":"tutorialSidebar"},{"id":"consulting","path":"/docs/next/consulting","sidebar":"tutorialSidebar"},{"id":"framework-features/api-versioning","path":"/docs/next/framework-features/api-versioning","sidebar":"tutorialSidebar"},{"id":"framework-features/code-generator","path":"/docs/next/framework-features/code-generator","sidebar":"tutorialSidebar"},{"id":"framework-features/etag","path":"/docs/next/framework-features/etag","sidebar":"tutorialSidebar"},{"id":"framework-features/index","path":"/docs/next/framework-features/","sidebar":"tutorialSidebar"},{"id":"framework-features/profiler","path":"/docs/next/framework-features/profiler","sidebar":"tutorialSidebar"},{"id":"framework-features/rate-limiting","path":"/docs/next/framework-features/rate-limiting","sidebar":"tutorialSidebar"},{"id":"framework-features/rbac","path":"/docs/next/framework-features/rbac","sidebar":"tutorialSidebar"},{"id":"getting-started/best-practices","path":"/docs/next/getting-started/best-practices","sidebar":"tutorialSidebar"},{"id":"getting-started/customized-laravel-components","path":"/docs/next/getting-started/customized-laravel-components","sidebar":"tutorialSidebar"},{"id":"getting-started/installation","path":"/docs/next/getting-started/installation","sidebar":"tutorialSidebar"},{"id":"pacakges/documentation","path":"/docs/next/pacakges/documentation","sidebar":"tutorialSidebar"},{"id":"pacakges/localization","path":"/docs/next/pacakges/localization","sidebar":"tutorialSidebar"},{"id":"pacakges/readme","path":"/docs/next/pacakges/","sidebar":"tutorialSidebar"},{"id":"pacakges/social-authentication","path":"/docs/next/pacakges/social-authentication","sidebar":"tutorialSidebar"},{"id":"prologue/contribution-guide","path":"/docs/next/prologue/contribution-guide","sidebar":"tutorialSidebar"},{"id":"prologue/release-notes","path":"/docs/next/prologue/release-notes","sidebar":"tutorialSidebar"},{"id":"prologue/upgrade-guide","path":"/docs/next/prologue/upgrade-guide","sidebar":"tutorialSidebar"},{"id":"security/authentication","path":"/docs/next/security/authentication","sidebar":"tutorialSidebar"},{"id":"security/authorization","path":"/docs/next/security/authorization","sidebar":"tutorialSidebar"},{"id":"security/email-varification","path":"/docs/next/security/email-varification","sidebar":"tutorialSidebar"},{"id":"security/hash-id","path":"/docs/next/security/hash-id","sidebar":"tutorialSidebar"},{"id":"security/password-reset","path":"/docs/next/security/password-reset","sidebar":"tutorialSidebar"},{"id":"security/registration","path":"/docs/next/security/registration","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/docs/next/prologue/release-notes","label":"prologue/release-notes"}}}},{"name":"12.x","label":"12.x","isLast":true,"path":"/docs","mainDocId":"prologue/release-notes","docs":[{"id":"architecture-concepts/components","path":"/docs/architecture-concepts/components","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/container","path":"/docs/architecture-concepts/container","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/porto","path":"/docs/architecture-concepts/porto","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/readme","path":"/docs/architecture-concepts/","sidebar":"tutorialSidebar"},{"id":"architecture-concepts/request-lifecycle","path":"/docs/architecture-concepts/request-lifecycle","sidebar":"tutorialSidebar"},{"id":"components/index","path":"/docs/components/","sidebar":"tutorialSidebar"},{"id":"components/main-components/actions","path":"/docs/components/main-components/actions","sidebar":"tutorialSidebar"},{"id":"components/main-components/controllers","path":"/docs/components/main-components/controllers","sidebar":"tutorialSidebar"},{"id":"components/main-components/exceptions","path":"/docs/components/main-components/exceptions","sidebar":"tutorialSidebar"},{"id":"components/main-components/index","path":"/docs/components/main-components/","sidebar":"tutorialSidebar"},{"id":"components/main-components/models","path":"/docs/components/main-components/models","sidebar":"tutorialSidebar"},{"id":"components/main-components/requests","path":"/docs/components/main-components/requests","sidebar":"tutorialSidebar"},{"id":"components/main-components/routes","path":"/docs/components/main-components/routes","sidebar":"tutorialSidebar"},{"id":"components/main-components/subactions","path":"/docs/components/main-components/subactions","sidebar":"tutorialSidebar"},{"id":"components/main-components/tasks","path":"/docs/components/main-components/tasks","sidebar":"tutorialSidebar"},{"id":"components/main-components/transformers","path":"/docs/components/main-components/transformers","sidebar":"tutorialSidebar"},{"id":"components/main-components/views","path":"/docs/components/main-components/views","sidebar":"tutorialSidebar"},{"id":"components/optional-components/commands","path":"/docs/components/optional-components/commands","sidebar":"tutorialSidebar"},{"id":"components/optional-components/configs","path":"/docs/components/optional-components/configs","sidebar":"tutorialSidebar"},{"id":"components/optional-components/events","path":"/docs/components/optional-components/events","sidebar":"tutorialSidebar"},{"id":"components/optional-components/factories","path":"/docs/components/optional-components/factories","sidebar":"tutorialSidebar"},{"id":"components/optional-components/helpers","path":"/docs/components/optional-components/helpers","sidebar":"tutorialSidebar"},{"id":"components/optional-components/index","path":"/docs/components/optional-components/","sidebar":"tutorialSidebar"},{"id":"components/optional-components/jobs","path":"/docs/components/optional-components/jobs","sidebar":"tutorialSidebar"},{"id":"components/optional-components/mail","path":"/docs/components/optional-components/mail","sidebar":"tutorialSidebar"},{"id":"components/optional-components/middlewares","path":"/docs/components/optional-components/middlewares","sidebar":"tutorialSidebar"},{"id":"components/optional-components/migrations","path":"/docs/components/optional-components/migrations","sidebar":"tutorialSidebar"},{"id":"components/optional-components/notifications","path":"/docs/components/optional-components/notifications","sidebar":"tutorialSidebar"},{"id":"components/optional-components/policies","path":"/docs/components/optional-components/policies","sidebar":"tutorialSidebar"},{"id":"components/optional-components/repository/criterias","path":"/docs/components/optional-components/repository/criterias","sidebar":"tutorialSidebar"},{"id":"components/optional-components/repository/repositories","path":"/docs/components/optional-components/repository/repositories","sidebar":"tutorialSidebar"},{"id":"components/optional-components/seeders","path":"/docs/components/optional-components/seeders","sidebar":"tutorialSidebar"},{"id":"components/optional-components/service-providers","path":"/docs/components/optional-components/service-providers","sidebar":"tutorialSidebar"},{"id":"components/optional-components/tests","path":"/docs/components/optional-components/tests","sidebar":"tutorialSidebar"},{"id":"components/optional-components/values","path":"/docs/components/optional-components/values","sidebar":"tutorialSidebar"},{"id":"consulting","path":"/docs/consulting","sidebar":"tutorialSidebar"},{"id":"framework-features/api-versioning","path":"/docs/framework-features/api-versioning","sidebar":"tutorialSidebar"},{"id":"framework-features/code-generator","path":"/docs/framework-features/code-generator","sidebar":"tutorialSidebar"},{"id":"framework-features/etag","path":"/docs/framework-features/etag","sidebar":"tutorialSidebar"},{"id":"framework-features/index","path":"/docs/framework-features/","sidebar":"tutorialSidebar"},{"id":"framework-features/profiler","path":"/docs/framework-features/profiler","sidebar":"tutorialSidebar"},{"id":"framework-features/rate-limiting","path":"/docs/framework-features/rate-limiting","sidebar":"tutorialSidebar"},{"id":"framework-features/rbac","path":"/docs/framework-features/rbac","sidebar":"tutorialSidebar"},{"id":"getting-started/best-practices","path":"/docs/getting-started/best-practices","sidebar":"tutorialSidebar"},{"id":"getting-started/customized-laravel-components","path":"/docs/getting-started/customized-laravel-components","sidebar":"tutorialSidebar"},{"id":"getting-started/installation","path":"/docs/getting-started/installation","sidebar":"tutorialSidebar"},{"id":"pacakges/documentation","path":"/docs/pacakges/documentation","sidebar":"tutorialSidebar"},{"id":"pacakges/localization","path":"/docs/pacakges/localization","sidebar":"tutorialSidebar"},{"id":"pacakges/readme","path":"/docs/pacakges/","sidebar":"tutorialSidebar"},{"id":"pacakges/social-authentication","path":"/docs/pacakges/social-authentication","sidebar":"tutorialSidebar"},{"id":"prologue/contribution-guide","path":"/docs/prologue/contribution-guide","sidebar":"tutorialSidebar"},{"id":"prologue/release-notes","path":"/docs/prologue/release-notes","sidebar":"tutorialSidebar"},{"id":"prologue/upgrade-guide","path":"/docs/prologue/upgrade-guide","sidebar":"tutorialSidebar"},{"id":"security/authentication","path":"/docs/security/authentication","sidebar":"tutorialSidebar"},{"id":"security/authorization","path":"/docs/security/authorization","sidebar":"tutorialSidebar"},{"id":"security/email-varification","path":"/docs/security/email-varification","sidebar":"tutorialSidebar"},{"id":"security/hash-id","path":"/docs/security/hash-id","sidebar":"tutorialSidebar"},{"id":"security/password-reset","path":"/docs/security/password-reset","sidebar":"tutorialSidebar"},{"id":"security/registration","path":"/docs/security/registration","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/docs/prologue/release-notes","label":"version-12.x/prologue/release-notes"}}}},{"name":"11.x","label":"11.x","isLast":false,"path":"/docs/11.x","mainDocId":"getting-started/requirements","docs":[{"id":"additional-features/documentation","path":"/docs/11.x/additional-features/documentation","sidebar":"version-11.x/docs"},{"id":"additional-features/localization","path":"/docs/11.x/additional-features/localization","sidebar":"version-11.x/docs"},{"id":"additional-features/overview","path":"/docs/11.x/additional-features/overview","sidebar":"version-11.x/docs"},{"id":"additional-features/social-authentication","path":"/docs/11.x/additional-features/social-authentication","sidebar":"version-11.x/docs"},{"id":"contribution-guide","path":"/docs/11.x/contribution-guide","sidebar":"version-11.x/docs"},{"id":"core-features/api-versioning","path":"/docs/11.x/core-features/api-versioning","sidebar":"version-11.x/docs"},{"id":"core-features/authentication","path":"/docs/11.x/core-features/authentication","sidebar":"version-11.x/docs"},{"id":"core-features/authorization","path":"/docs/11.x/core-features/authorization","sidebar":"version-11.x/docs"},{"id":"core-features/code-generator","path":"/docs/11.x/core-features/code-generator","sidebar":"version-11.x/docs"},{"id":"core-features/data-caching","path":"/docs/11.x/core-features/data-caching","sidebar":"version-11.x/docs"},{"id":"core-features/default-endpoints","path":"/docs/11.x/core-features/default-endpoints","sidebar":"version-11.x/docs"},{"id":"core-features/etag","path":"/docs/11.x/core-features/etag","sidebar":"version-11.x/docs"},{"id":"core-features/hash-id","path":"/docs/11.x/core-features/hash-id","sidebar":"version-11.x/docs"},{"id":"core-features/pagination","path":"/docs/11.x/core-features/pagination","sidebar":"version-11.x/docs"},{"id":"core-features/profiler","path":"/docs/11.x/core-features/profiler","sidebar":"version-11.x/docs"},{"id":"core-features/query-parameters","path":"/docs/11.x/core-features/query-parameters","sidebar":"version-11.x/docs"},{"id":"core-features/rate-limiting","path":"/docs/11.x/core-features/rate-limiting","sidebar":"version-11.x/docs"},{"id":"core-features/useful-commands","path":"/docs/11.x/core-features/useful-commands","sidebar":"version-11.x/docs"},{"id":"core-features/user-registration","path":"/docs/11.x/core-features/user-registration","sidebar":"version-11.x/docs"},{"id":"core-features/validation","path":"/docs/11.x/core-features/validation","sidebar":"version-11.x/docs"},{"id":"faq","path":"/docs/11.x/faq","sidebar":"version-11.x/docs"},{"id":"getting-started/container-installer","path":"/docs/11.x/getting-started/container-installer","sidebar":"version-11.x/docs"},{"id":"getting-started/conventions-and-principles","path":"/docs/11.x/getting-started/conventions-and-principles","sidebar":"version-11.x/docs"},{"id":"getting-started/installation","path":"/docs/11.x/getting-started/installation","sidebar":"version-11.x/docs"},{"id":"getting-started/markdown-features","path":"/docs/11.x/getting-started/markdown-features"},{"id":"getting-started/requests","path":"/docs/11.x/getting-started/requests","sidebar":"version-11.x/docs"},{"id":"getting-started/requirements","path":"/docs/11.x/","sidebar":"version-11.x/docs"},{"id":"getting-started/responses","path":"/docs/11.x/getting-started/responses","sidebar":"version-11.x/docs"},{"id":"getting-started/samples","path":"/docs/11.x/getting-started/samples","sidebar":"version-11.x/docs"},{"id":"getting-started/software-architectural-patterns","path":"/docs/11.x/getting-started/software-architectural-patterns","sidebar":"version-11.x/docs"},{"id":"main-components/actions","path":"/docs/11.x/main-components/actions","sidebar":"version-11.x/docs"},{"id":"main-components/controllers","path":"/docs/11.x/main-components/controllers","sidebar":"version-11.x/docs"},{"id":"main-components/exceptions","path":"/docs/11.x/main-components/exceptions","sidebar":"version-11.x/docs"},{"id":"main-components/models","path":"/docs/11.x/main-components/models","sidebar":"version-11.x/docs"},{"id":"main-components/requests","path":"/docs/11.x/main-components/requests","sidebar":"version-11.x/docs"},{"id":"main-components/routes","path":"/docs/11.x/main-components/routes","sidebar":"version-11.x/docs"},{"id":"main-components/subactions","path":"/docs/11.x/main-components/subactions","sidebar":"version-11.x/docs"},{"id":"main-components/tasks","path":"/docs/11.x/main-components/tasks","sidebar":"version-11.x/docs"},{"id":"main-components/transformers","path":"/docs/11.x/main-components/transformers","sidebar":"version-11.x/docs"},{"id":"main-components/views","path":"/docs/11.x/main-components/views","sidebar":"version-11.x/docs"},{"id":"miscellaneous/tasks-queuing","path":"/docs/11.x/miscellaneous/tasks-queuing","sidebar":"version-11.x/docs"},{"id":"miscellaneous/tasks-scheduling","path":"/docs/11.x/miscellaneous/tasks-scheduling","sidebar":"version-11.x/docs"},{"id":"miscellaneous/tests-helpers","path":"/docs/11.x/miscellaneous/tests-helpers","sidebar":"version-11.x/docs"},{"id":"optional-components/commands","path":"/docs/11.x/optional-components/commands","sidebar":"version-11.x/docs"},{"id":"optional-components/configs","path":"/docs/11.x/optional-components/configs","sidebar":"version-11.x/docs"},{"id":"optional-components/criterias","path":"/docs/11.x/optional-components/criterias","sidebar":"version-11.x/docs"},{"id":"optional-components/events","path":"/docs/11.x/optional-components/events","sidebar":"version-11.x/docs"},{"id":"optional-components/factories","path":"/docs/11.x/optional-components/factories","sidebar":"version-11.x/docs"},{"id":"optional-components/helpers","path":"/docs/11.x/optional-components/helpers","sidebar":"version-11.x/docs"},{"id":"optional-components/jobs","path":"/docs/11.x/optional-components/jobs","sidebar":"version-11.x/docs"},{"id":"optional-components/languages","path":"/docs/11.x/optional-components/languages","sidebar":"version-11.x/docs"},{"id":"optional-components/mails","path":"/docs/11.x/optional-components/mails","sidebar":"version-11.x/docs"},{"id":"optional-components/middlewares","path":"/docs/11.x/optional-components/middlewares","sidebar":"version-11.x/docs"},{"id":"optional-components/migrations","path":"/docs/11.x/optional-components/migrations","sidebar":"version-11.x/docs"},{"id":"optional-components/notifications","path":"/docs/11.x/optional-components/notifications","sidebar":"version-11.x/docs"},{"id":"optional-components/providers","path":"/docs/11.x/optional-components/providers","sidebar":"version-11.x/docs"},{"id":"optional-components/repositories","path":"/docs/11.x/optional-components/repositories","sidebar":"version-11.x/docs"},{"id":"optional-components/seeders","path":"/docs/11.x/optional-components/seeders","sidebar":"version-11.x/docs"},{"id":"optional-components/tests","path":"/docs/11.x/optional-components/tests","sidebar":"version-11.x/docs"},{"id":"optional-components/values","path":"/docs/11.x/optional-components/values","sidebar":"version-11.x/docs"},{"id":"upgrade-guide","path":"/docs/11.x/upgrade-guide","sidebar":"version-11.x/docs"}],"draftIds":[],"sidebars":{"version-11.x/docs":{"link":{"path":"/docs/11.x/","label":"version-11.x/getting-started/requirements"}}}},{"name":"10.x","label":"10.x","isLast":false,"path":"/docs/10.x","mainDocId":"getting-started/requirements","docs":[{"id":"additional-features/apiato-containers/debugger","path":"/docs/10.x/additional-features/apiato-containers/debugger","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/documentation","path":"/docs/10.x/additional-features/apiato-containers/documentation","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/localization","path":"/docs/10.x/additional-features/apiato-containers/localization","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/overview","path":"/docs/10.x/additional-features/apiato-containers/overview","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/payments","path":"/docs/10.x/additional-features/apiato-containers/payments","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/settings","path":"/docs/10.x/additional-features/apiato-containers/settings","sidebar":"version-10.x/docs"},{"id":"additional-features/apiato-containers/social-authentication","path":"/docs/10.x/additional-features/apiato-containers/social-authentication","sidebar":"version-10.x/docs"},{"id":"additional-features/community-containers/overview","path":"/docs/10.x/additional-features/community-containers/overview","sidebar":"version-10.x/docs"},{"id":"contribution-guide","path":"/docs/10.x/contribution-guide","sidebar":"version-10.x/docs"},{"id":"core-features/api-versioning","path":"/docs/10.x/core-features/api-versioning","sidebar":"version-10.x/docs"},{"id":"core-features/authentication","path":"/docs/10.x/core-features/authentication","sidebar":"version-10.x/docs"},{"id":"core-features/authorization","path":"/docs/10.x/core-features/authorization","sidebar":"version-10.x/docs"},{"id":"core-features/code-generator","path":"/docs/10.x/core-features/code-generator","sidebar":"version-10.x/docs"},{"id":"core-features/data-caching","path":"/docs/10.x/core-features/data-caching","sidebar":"version-10.x/docs"},{"id":"core-features/default-endpoints","path":"/docs/10.x/core-features/default-endpoints","sidebar":"version-10.x/docs"},{"id":"core-features/etag","path":"/docs/10.x/core-features/etag","sidebar":"version-10.x/docs"},{"id":"core-features/hash-id","path":"/docs/10.x/core-features/hash-id","sidebar":"version-10.x/docs"},{"id":"core-features/pagination","path":"/docs/10.x/core-features/pagination","sidebar":"version-10.x/docs"},{"id":"core-features/profiler","path":"/docs/10.x/core-features/profiler","sidebar":"version-10.x/docs"},{"id":"core-features/query-parameters","path":"/docs/10.x/core-features/query-parameters","sidebar":"version-10.x/docs"},{"id":"core-features/rate-limiting","path":"/docs/10.x/core-features/rate-limiting","sidebar":"version-10.x/docs"},{"id":"core-features/useful-commands","path":"/docs/10.x/core-features/useful-commands","sidebar":"version-10.x/docs"},{"id":"core-features/user-registration","path":"/docs/10.x/core-features/user-registration","sidebar":"version-10.x/docs"},{"id":"core-features/validation","path":"/docs/10.x/core-features/validation","sidebar":"version-10.x/docs"},{"id":"faq","path":"/docs/10.x/faq","sidebar":"version-10.x/docs"},{"id":"getting-started/container-installer","path":"/docs/10.x/getting-started/container-installer","sidebar":"version-10.x/docs"},{"id":"getting-started/conventions-and-principles","path":"/docs/10.x/getting-started/conventions-and-principles","sidebar":"version-10.x/docs"},{"id":"getting-started/installation","path":"/docs/10.x/getting-started/installation","sidebar":"version-10.x/docs"},{"id":"getting-started/markdown-features","path":"/docs/10.x/getting-started/markdown-features"},{"id":"getting-started/requests","path":"/docs/10.x/getting-started/requests","sidebar":"version-10.x/docs"},{"id":"getting-started/requirements","path":"/docs/10.x/","sidebar":"version-10.x/docs"},{"id":"getting-started/responses","path":"/docs/10.x/getting-started/responses","sidebar":"version-10.x/docs"},{"id":"getting-started/samples","path":"/docs/10.x/getting-started/samples","sidebar":"version-10.x/docs"},{"id":"getting-started/software-architectural-patterns","path":"/docs/10.x/getting-started/software-architectural-patterns","sidebar":"version-10.x/docs"},{"id":"main-components/actions","path":"/docs/10.x/main-components/actions","sidebar":"version-10.x/docs"},{"id":"main-components/controllers","path":"/docs/10.x/main-components/controllers","sidebar":"version-10.x/docs"},{"id":"main-components/exceptions","path":"/docs/10.x/main-components/exceptions","sidebar":"version-10.x/docs"},{"id":"main-components/models","path":"/docs/10.x/main-components/models","sidebar":"version-10.x/docs"},{"id":"main-components/requests","path":"/docs/10.x/main-components/requests","sidebar":"version-10.x/docs"},{"id":"main-components/routes","path":"/docs/10.x/main-components/routes","sidebar":"version-10.x/docs"},{"id":"main-components/subactions","path":"/docs/10.x/main-components/subactions","sidebar":"version-10.x/docs"},{"id":"main-components/tasks","path":"/docs/10.x/main-components/tasks","sidebar":"version-10.x/docs"},{"id":"main-components/transformers","path":"/docs/10.x/main-components/transformers","sidebar":"version-10.x/docs"},{"id":"main-components/views","path":"/docs/10.x/main-components/views","sidebar":"version-10.x/docs"},{"id":"miscellaneous/tasks-queuing","path":"/docs/10.x/miscellaneous/tasks-queuing","sidebar":"version-10.x/docs"},{"id":"miscellaneous/tasks-scheduling","path":"/docs/10.x/miscellaneous/tasks-scheduling","sidebar":"version-10.x/docs"},{"id":"miscellaneous/tests-helpers","path":"/docs/10.x/miscellaneous/tests-helpers","sidebar":"version-10.x/docs"},{"id":"optional-components/commands","path":"/docs/10.x/optional-components/commands","sidebar":"version-10.x/docs"},{"id":"optional-components/configs","path":"/docs/10.x/optional-components/configs","sidebar":"version-10.x/docs"},{"id":"optional-components/criterias","path":"/docs/10.x/optional-components/criterias","sidebar":"version-10.x/docs"},{"id":"optional-components/events","path":"/docs/10.x/optional-components/events","sidebar":"version-10.x/docs"},{"id":"optional-components/factories","path":"/docs/10.x/optional-components/factories","sidebar":"version-10.x/docs"},{"id":"optional-components/helpers","path":"/docs/10.x/optional-components/helpers","sidebar":"version-10.x/docs"},{"id":"optional-components/jobs","path":"/docs/10.x/optional-components/jobs","sidebar":"version-10.x/docs"},{"id":"optional-components/languages","path":"/docs/10.x/optional-components/languages","sidebar":"version-10.x/docs"},{"id":"optional-components/mails","path":"/docs/10.x/optional-components/mails","sidebar":"version-10.x/docs"},{"id":"optional-components/middlewares","path":"/docs/10.x/optional-components/middlewares","sidebar":"version-10.x/docs"},{"id":"optional-components/migrations","path":"/docs/10.x/optional-components/migrations","sidebar":"version-10.x/docs"},{"id":"optional-components/notifications","path":"/docs/10.x/optional-components/notifications","sidebar":"version-10.x/docs"},{"id":"optional-components/providers","path":"/docs/10.x/optional-components/providers","sidebar":"version-10.x/docs"},{"id":"optional-components/repositories","path":"/docs/10.x/optional-components/repositories","sidebar":"version-10.x/docs"},{"id":"optional-components/seeders","path":"/docs/10.x/optional-components/seeders","sidebar":"version-10.x/docs"},{"id":"optional-components/tests","path":"/docs/10.x/optional-components/tests","sidebar":"version-10.x/docs"},{"id":"optional-components/values","path":"/docs/10.x/optional-components/values","sidebar":"version-10.x/docs"},{"id":"upgrade-guide","path":"/docs/10.x/upgrade-guide","sidebar":"version-10.x/docs"}],"draftIds":[],"sidebars":{"version-10.x/docs":{"link":{"path":"/docs/10.x/","label":"version-10.x/getting-started/requirements"}}}},{"name":"9.x","label":"9.x","isLast":false,"path":"/docs/9.x","mainDocId":"getting-started/requirements","docs":[{"id":"contribution-guide","path":"/docs/9.x/contribution-guide","sidebar":"version-9.x/docs"},{"id":"core-features/admin-dashboard","path":"/docs/9.x/core-features/admin-dashboard","sidebar":"version-9.x/docs"},{"id":"core-features/api-docs-generator","path":"/docs/9.x/core-features/api-docs-generator","sidebar":"version-9.x/docs"},{"id":"core-features/api-versioning","path":"/docs/9.x/core-features/api-versioning","sidebar":"version-9.x/docs"},{"id":"core-features/authentication","path":"/docs/9.x/core-features/authentication","sidebar":"version-9.x/docs"},{"id":"core-features/authorization","path":"/docs/9.x/core-features/authorization","sidebar":"version-9.x/docs"},{"id":"core-features/code-generator","path":"/docs/9.x/core-features/code-generator","sidebar":"version-9.x/docs"},{"id":"core-features/data-caching","path":"/docs/9.x/core-features/data-caching","sidebar":"version-9.x/docs"},{"id":"core-features/default-endpoints","path":"/docs/9.x/core-features/default-endpoints","sidebar":"version-9.x/docs"},{"id":"core-features/etag","path":"/docs/9.x/core-features/etag","sidebar":"version-9.x/docs"},{"id":"core-features/hash-id","path":"/docs/9.x/core-features/hash-id","sidebar":"version-9.x/docs"},{"id":"core-features/localization","path":"/docs/9.x/core-features/localization","sidebar":"version-9.x/docs"},{"id":"core-features/pagination","path":"/docs/9.x/core-features/pagination","sidebar":"version-9.x/docs"},{"id":"core-features/payments","path":"/docs/9.x/core-features/payments","sidebar":"version-9.x/docs"},{"id":"core-features/profiler","path":"/docs/9.x/core-features/profiler","sidebar":"version-9.x/docs"},{"id":"core-features/query-parameters","path":"/docs/9.x/core-features/query-parameters","sidebar":"version-9.x/docs"},{"id":"core-features/rate-limiting","path":"/docs/9.x/core-features/rate-limiting","sidebar":"version-9.x/docs"},{"id":"core-features/requests-monitor","path":"/docs/9.x/core-features/requests-monitor","sidebar":"version-9.x/docs"},{"id":"core-features/search-query-parameter","path":"/docs/9.x/core-features/search-query-parameter","sidebar":"version-9.x/docs"},{"id":"core-features/social-authentication","path":"/docs/9.x/core-features/social-authentication","sidebar":"version-9.x/docs"},{"id":"core-features/system-settings","path":"/docs/9.x/core-features/system-settings","sidebar":"version-9.x/docs"},{"id":"core-features/useful-commands","path":"/docs/9.x/core-features/useful-commands","sidebar":"version-9.x/docs"},{"id":"core-features/user-registration","path":"/docs/9.x/core-features/user-registration","sidebar":"version-9.x/docs"},{"id":"core-features/validation","path":"/docs/9.x/core-features/validation","sidebar":"version-9.x/docs"},{"id":"faq","path":"/docs/9.x/faq","sidebar":"version-9.x/docs"},{"id":"getting-started/conventions-and-principles","path":"/docs/9.x/getting-started/conventions-and-principles","sidebar":"version-9.x/docs"},{"id":"getting-started/installation","path":"/docs/9.x/getting-started/installation","sidebar":"version-9.x/docs"},{"id":"getting-started/markdown-features","path":"/docs/9.x/getting-started/markdown-features"},{"id":"getting-started/overview","path":"/docs/9.x/getting-started/overview","sidebar":"version-9.x/docs"},{"id":"getting-started/requests","path":"/docs/9.x/getting-started/requests","sidebar":"version-9.x/docs"},{"id":"getting-started/requirements","path":"/docs/9.x/","sidebar":"version-9.x/docs"},{"id":"getting-started/responses","path":"/docs/9.x/getting-started/responses","sidebar":"version-9.x/docs"},{"id":"getting-started/software-architectural-patterns","path":"/docs/9.x/getting-started/software-architectural-patterns","sidebar":"version-9.x/docs"},{"id":"main-components/actions","path":"/docs/9.x/main-components/actions","sidebar":"version-9.x/docs"},{"id":"main-components/controllers","path":"/docs/9.x/main-components/controllers","sidebar":"version-9.x/docs"},{"id":"main-components/models","path":"/docs/9.x/main-components/models","sidebar":"version-9.x/docs"},{"id":"main-components/requests","path":"/docs/9.x/main-components/requests","sidebar":"version-9.x/docs"},{"id":"main-components/routes","path":"/docs/9.x/main-components/routes","sidebar":"version-9.x/docs"},{"id":"main-components/subactions","path":"/docs/9.x/main-components/subactions","sidebar":"version-9.x/docs"},{"id":"main-components/tasks","path":"/docs/9.x/main-components/tasks","sidebar":"version-9.x/docs"},{"id":"main-components/transformers","path":"/docs/9.x/main-components/transformers","sidebar":"version-9.x/docs"},{"id":"main-components/transporters","path":"/docs/9.x/main-components/transporters","sidebar":"version-9.x/docs"},{"id":"main-components/views","path":"/docs/9.x/main-components/views","sidebar":"version-9.x/docs"},{"id":"miscellaneous/container-installer","path":"/docs/9.x/miscellaneous/container-installer","sidebar":"version-9.x/docs"},{"id":"miscellaneous/magical-call","path":"/docs/9.x/miscellaneous/magical-call","sidebar":"version-9.x/docs"},{"id":"miscellaneous/postman","path":"/docs/9.x/miscellaneous/postman","sidebar":"version-9.x/docs"},{"id":"miscellaneous/tasks-queuing","path":"/docs/9.x/miscellaneous/tasks-queuing","sidebar":"version-9.x/docs"},{"id":"miscellaneous/tasks-scheduling","path":"/docs/9.x/miscellaneous/tasks-scheduling","sidebar":"version-9.x/docs"},{"id":"miscellaneous/tests-helpers","path":"/docs/9.x/miscellaneous/tests-helpers","sidebar":"version-9.x/docs"},{"id":"optional-components/commands","path":"/docs/9.x/optional-components/commands","sidebar":"version-9.x/docs"},{"id":"optional-components/configs","path":"/docs/9.x/optional-components/configs","sidebar":"version-9.x/docs"},{"id":"optional-components/criterias","path":"/docs/9.x/optional-components/criterias","sidebar":"version-9.x/docs"},{"id":"optional-components/events","path":"/docs/9.x/optional-components/events","sidebar":"version-9.x/docs"},{"id":"optional-components/exceptions","path":"/docs/9.x/optional-components/exceptions","sidebar":"version-9.x/docs"},{"id":"optional-components/factories","path":"/docs/9.x/optional-components/factories","sidebar":"version-9.x/docs"},{"id":"optional-components/jobs","path":"/docs/9.x/optional-components/jobs","sidebar":"version-9.x/docs"},{"id":"optional-components/languages","path":"/docs/9.x/optional-components/languages","sidebar":"version-9.x/docs"},{"id":"optional-components/mails","path":"/docs/9.x/optional-components/mails","sidebar":"version-9.x/docs"},{"id":"optional-components/middlewares","path":"/docs/9.x/optional-components/middlewares","sidebar":"version-9.x/docs"},{"id":"optional-components/migrations","path":"/docs/9.x/optional-components/migrations","sidebar":"version-9.x/docs"},{"id":"optional-components/notifications","path":"/docs/9.x/optional-components/notifications","sidebar":"version-9.x/docs"},{"id":"optional-components/providers","path":"/docs/9.x/optional-components/providers","sidebar":"version-9.x/docs"},{"id":"optional-components/repositories","path":"/docs/9.x/optional-components/repositories","sidebar":"version-9.x/docs"},{"id":"optional-components/seeders","path":"/docs/9.x/optional-components/seeders","sidebar":"version-9.x/docs"},{"id":"optional-components/tests","path":"/docs/9.x/optional-components/tests","sidebar":"version-9.x/docs"},{"id":"optional-components/values","path":"/docs/9.x/optional-components/values","sidebar":"version-9.x/docs"},{"id":"upgrade-guide","path":"/docs/9.x/upgrade-guide","sidebar":"version-9.x/docs"}],"draftIds":[],"sidebars":{"version-9.x/docs":{"link":{"path":"/docs/9.x/","label":"version-9.x/getting-started/requirements"}}}}],"breadcrumbs":true}}}'),s=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var i=n(57529);const c=JSON.parse('{"docusaurusVersion":"2.4.3","siteVersion":"1.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"2.4.3"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"2.4.3"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"2.4.3"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"2.4.3"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"2.4.3"},"docusaurus-theme-search-algolia":{"type":"package","name":"@docusaurus/theme-search-algolia","version":"2.4.3"},"docusaurus-theme-mermaid":{"type":"package","name":"@docusaurus/theme-mermaid","version":"2.4.3"}}}'),l={siteConfig:a.default,siteMetadata:c,globalData:r,i18n:s,codeTranslations:i},d=o.createContext(l);function u(e){let{children:t}=e;return o.createElement(d.Provider,{value:l},t)}},44763:(e,t,n)=>{"use strict";n.d(t,{Z:()=>p});var o=n(67294),a=n(10412),r=n(35742),s=n(18780),i=n(49037);function c(e){let{error:t,tryAgain:n}=e;return o.createElement("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"}},o.createElement("h1",{style:{fontSize:"3rem"}},"This page crashed"),o.createElement("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"}},"Try again"),o.createElement(l,{error:t}))}function l(e){let{error:t}=e;const n=(0,s.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return o.createElement("p",{style:{whiteSpace:"pre-wrap"}},n)}function d(e){let{error:t,tryAgain:n}=e;return o.createElement(p,{fallback:()=>o.createElement(c,{error:t,tryAgain:n})},o.createElement(r.Z,null,o.createElement("title",null,"Page Error")),o.createElement(i.Z,null,o.createElement(c,{error:t,tryAgain:n})))}const u=e=>o.createElement(d,e);class p extends o.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.Z.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??u)(e)}return e??null}}},10412:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});const o="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:o,canUseEventListeners:o&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:o&&"IntersectionObserver"in window,canUseViewport:o&&"screen"in window}},35742:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});var o=n(67294),a=n(70405);function r(e){return o.createElement(a.ql,e)}},39960:(e,t,n)=>{"use strict";n.d(t,{Z:()=>m});var o=n(87462),a=n(67294),r=n(73727),s=n(18780),i=n(52263),c=n(13919),l=n(10412);const d=a.createContext({collectLink:()=>{}});var u=n(44996);function p(e,t){let{isNavLink:n,to:p,href:m,activeClassName:f,isActive:g,"data-noBrokenLinkCheck":h,autoAddBaseUrl:b=!0,...v}=e;const{siteConfig:{trailingSlash:x,baseUrl:y}}=(0,i.Z)(),{withBaseUrl:_}=(0,u.C)(),w=(0,a.useContext)(d),k=(0,a.useRef)(null);(0,a.useImperativeHandle)(t,(()=>k.current));const S=p||m;const E=(0,c.Z)(S),T=S?.replace("pathname://","");let C=void 0!==T?(A=T,b&&(e=>e.startsWith("/"))(A)?_(A):A):void 0;var A;C&&E&&(C=(0,s.applyTrailingSlash)(C,{trailingSlash:x,baseUrl:y}));const L=(0,a.useRef)(!1),O=n?r.OL:r.rU,N=l.Z.canUseIntersectionObserver,P=(0,a.useRef)(),I=()=>{L.current||null==C||(window.docusaurus.preload(C),L.current=!0)};(0,a.useEffect)((()=>(!N&&E&&null!=C&&window.docusaurus.prefetch(C),()=>{N&&P.current&&P.current.disconnect()})),[P,C,N,E]);const R=C?.startsWith("#")??!1,M=!C||!E||R;return M||h||w.collectLink(C),M?a.createElement("a",(0,o.Z)({ref:k,href:C},S&&!E&&{target:"_blank",rel:"noopener noreferrer"},v)):a.createElement(O,(0,o.Z)({},v,{onMouseEnter:I,onTouchStart:I,innerRef:e=>{k.current=e,N&&e&&E&&(P.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(P.current.unobserve(e),P.current.disconnect(),null!=C&&window.docusaurus.prefetch(C))}))})),P.current.observe(e))},to:C},n&&{isActive:g,activeClassName:f}))}const m=a.forwardRef(p)},95999:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c,I:()=>i});var o=n(67294);function a(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,o.isValidElement)(e)))?n.map(((e,t)=>(0,o.isValidElement)(e)?o.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var r=n(57529);function s(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return r[t??n]??n??t}function i(e,t){let{message:n,id:o}=e;return a(s({message:n,id:o}),t)}function c(e){let{children:t,id:n,values:r}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal children",t),new Error("The Docusaurus component only accept simple string values");const i=s({message:t,id:n});return o.createElement(o.Fragment,null,a(i,r))}},29935:(e,t,n)=>{"use strict";n.d(t,{m:()=>o});const o="default"},13919:(e,t,n)=>{"use strict";function o(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!o(e)}n.d(t,{Z:()=>a,b:()=>o})},44996:(e,t,n)=>{"use strict";n.d(t,{C:()=>s,Z:()=>i});var o=n(67294),a=n(52263),r=n(13919);function s(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.Z)(),n=(0,o.useCallback)(((n,o)=>function(e,t,n,o){let{forcePrependBaseUrl:a=!1,absolute:s=!1}=void 0===o?{}:o;if(!n||n.startsWith("#")||(0,r.b)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const i=n.startsWith(t)?n:t+n.replace(/^\//,"");return s?e+i:i}(t,e,n,o)),[t,e]);return{withBaseUrl:n}}function i(e,t){void 0===t&&(t={});const{withBaseUrl:n}=s();return n(e,t)}},52263:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});var o=n(67294),a=n(58940);function r(){return(0,o.useContext)(a._)}},72389:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});var o=n(67294),a=n(98934);function r(){return(0,o.useContext)(a._)}},99670:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});const o=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach((n=>{let[r,s]=n;const i=a?`${a}.${r}`:r;o(s)?e(s,i):t[i]=s}))}(e),t}},30226:(e,t,n)=>{"use strict";n.d(t,{_:()=>a,z:()=>r});var o=n(67294);const a=o.createContext(null);function r(e){let{children:t,value:n}=e;const r=o.useContext(a),s=(0,o.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const o={...t.data,...n?.data};return{plugin:t.plugin,data:o}}({parent:r,value:n})),[r,n]);return o.createElement(a.Provider,{value:s},t)}},80143:(e,t,n)=>{"use strict";n.d(t,{Iw:()=>b,gA:()=>m,WS:()=>f,_r:()=>u,Jo:()=>v,zh:()=>p,yW:()=>h,gB:()=>g});var o=n(16550),a=n(52263),r=n(29935);function s(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,a.Z)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const i=e=>e.versions.find((e=>e.isLast));function c(e,t){const n=i(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,o.LX)(t,{path:e.path,exact:!1,strict:!1})))}function l(e,t){const n=c(e,t),a=n?.docs.find((e=>!!(0,o.LX)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((o=>{o.id===t&&(n[e.name]=o)}))})),n}(a.id):{}}}const d={},u=()=>s("docusaurus-plugin-content-docs")??d,p=e=>function(e,t,n){void 0===t&&(t=r.m),void 0===n&&(n={});const o=s(e),a=o?.[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0});function m(e){void 0===e&&(e={});const t=u(),{pathname:n}=(0,o.TH)();return function(e,t,n){void 0===n&&(n={});const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,o.LX)(t,{path:n.path,exact:!1,strict:!1})})),r=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!r&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return r}(t,n,e)}function f(e){void 0===e&&(e={});const t=m(e),{pathname:n}=(0,o.TH)();if(!t)return;return{activePlugin:t,activeVersion:c(t.pluginData,n)}}function g(e){return p(e).versions}function h(e){const t=p(e);return i(t)}function b(e){const t=p(e),{pathname:n}=(0,o.TH)();return l(t,n)}function v(e){const t=p(e),{pathname:n}=(0,o.TH)();return function(e,t){const n=i(e);return{latestDocSuggestion:l(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},18320:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});var o=n(74865),a=n.n(o);a().configure({showSpinner:!1});const r={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},3310:(e,t,n)=>{"use strict";n.r(t);var o=n(87410),a=n(36809);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:o}=t;globalThis.Prism=e,o.forEach((e=>{n(48682)(`./prism-${e}`)})),delete globalThis.Prism}(o.Z)},39471:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});var o=n(67294);const a={iconExternalLink:"iconExternalLink_nPIU"};function r(e){let{width:t=13.5,height:n=13.5}=e;return o.createElement("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:a.iconExternalLink},o.createElement("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"}))}},49037:(e,t,n)=>{"use strict";n.d(t,{Z:()=>Ot});var o=n(67294),a=n(86010),r=n(44763),s=n(1944),i=n(87462),c=n(16550),l=n(95999),d=n(85936);const u="__docusaurus_skipToContent_fallback";function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function m(){const e=(0,o.useRef)(null),{action:t}=(0,c.k6)(),n=(0,o.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(u);t&&p(t)}),[]);return(0,d.S)((n=>{let{location:o}=n;e.current&&!o.hash&&"PUSH"===t&&p(e.current)})),{containerRef:e,onClick:n}}const f=(0,l.I)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function g(e){const t=e.children??f,{containerRef:n,onClick:a}=m();return o.createElement("div",{ref:n,role:"region","aria-label":f},o.createElement("a",(0,i.Z)({},e,{href:`#${u}`,onClick:a}),t))}var h=n(35281),b=n(19727);const v={skipToContent:"skipToContent_fXgn"};function x(){return o.createElement(g,{className:v.skipToContent})}var y=n(86668),_=n(59689);function w(e){let{width:t=21,height:n=21,color:a="currentColor",strokeWidth:r=1.2,className:s,...c}=e;return o.createElement("svg",(0,i.Z)({viewBox:"0 0 15 15",width:t,height:n},c),o.createElement("g",{stroke:a,strokeWidth:r},o.createElement("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})))}const k={closeButton:"closeButton_CVFx"};function S(e){return o.createElement("button",(0,i.Z)({type:"button","aria-label":(0,l.I)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"})},e,{className:(0,a.Z)("clean-btn close",k.closeButton,e.className)}),o.createElement(w,{width:14,height:14,strokeWidth:3.1}))}const E={content:"content_knG7"};function T(e){const{announcementBar:t}=(0,y.L)(),{content:n}=t;return o.createElement("div",(0,i.Z)({},e,{className:(0,a.Z)(E.content,e.className),dangerouslySetInnerHTML:{__html:n}}))}const C={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function A(){const{announcementBar:e}=(0,y.L)(),{isActive:t,close:n}=(0,_.nT)();if(!t)return null;const{backgroundColor:a,textColor:r,isCloseable:s}=e;return o.createElement("div",{className:C.announcementBar,style:{backgroundColor:a,color:r},role:"banner"},s&&o.createElement("div",{className:C.announcementBarPlaceholder}),o.createElement(T,{className:C.announcementBarContent}),s&&o.createElement(S,{onClick:n,className:C.announcementBarClose}))}var L=n(72961),O=n(12466);var N=n(902),P=n(13102);const I=o.createContext(null);function R(e){let{children:t}=e;const n=function(){const e=(0,L.e)(),t=(0,P.HY)(),[n,a]=(0,o.useState)(!1),r=null!==t.component,s=(0,N.D9)(r);return(0,o.useEffect)((()=>{r&&!s&&a(!0)}),[r,s]),(0,o.useEffect)((()=>{r?e.shown||a(!0):a(!1)}),[e.shown,r]),(0,o.useMemo)((()=>[n,a]),[n])}();return o.createElement(I.Provider,{value:n},t)}function M(e){if(e.component){const t=e.component;return o.createElement(t,e.props)}}function D(){const e=(0,o.useContext)(I);if(!e)throw new N.i6("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,o.useCallback)((()=>n(!1)),[n]),r=(0,P.HY)();return(0,o.useMemo)((()=>({shown:t,hide:a,content:M(r)})),[a,r,t])}function B(e){let{header:t,primaryMenu:n,secondaryMenu:r}=e;const{shown:s}=D();return o.createElement("div",{className:"navbar-sidebar"},t,o.createElement("div",{className:(0,a.Z)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":s})},o.createElement("div",{className:"navbar-sidebar__item menu"},n),o.createElement("div",{className:"navbar-sidebar__item menu"},r)))}var j=n(92949),F=n(72389);function z(e){return o.createElement("svg",(0,i.Z)({viewBox:"0 0 24 24",width:24,height:24},e),o.createElement("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"}))}function $(e){return o.createElement("svg",(0,i.Z)({viewBox:"0 0 24 24",width:24,height:24},e),o.createElement("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"}))}const U={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function q(e){let{className:t,buttonClassName:n,value:r,onChange:s}=e;const i=(0,F.Z)(),c=(0,l.I)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===r?(0,l.I)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,l.I)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return o.createElement("div",{className:(0,a.Z)(U.toggle,t)},o.createElement("button",{className:(0,a.Z)("clean-btn",U.toggleButton,!i&&U.toggleButtonDisabled,n),type:"button",onClick:()=>s("dark"===r?"light":"dark"),disabled:!i,title:c,"aria-label":c,"aria-live":"polite"},o.createElement(z,{className:(0,a.Z)(U.toggleIcon,U.lightToggleIcon)}),o.createElement($,{className:(0,a.Z)(U.toggleIcon,U.darkToggleIcon)})))}const H=o.memo(q),Z={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function V(e){let{className:t}=e;const n=(0,y.L)().navbar.style,a=(0,y.L)().colorMode.disableSwitch,{colorMode:r,setColorMode:s}=(0,j.I)();return a?null:o.createElement(H,{className:t,buttonClassName:"dark"===n?Z.darkNavbarColorModeToggle:void 0,value:r,onChange:s})}var G=n(21327);function W(){return o.createElement(G.Z,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function Y(){const e=(0,L.e)();return o.createElement("button",{type:"button","aria-label":(0,l.I)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle()},o.createElement(w,{color:"var(--ifm-color-emphasis-600)"}))}function K(){return o.createElement("div",{className:"navbar-sidebar__brand"},o.createElement(W,null),o.createElement(V,{className:"margin-right--md"}),o.createElement(Y,null))}var X=n(39960),Q=n(44996),J=n(13919);function ee(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var te=n(39471);function ne(e){let{activeBasePath:t,activeBaseRegex:n,to:a,href:r,label:s,html:c,isDropdownLink:l,prependBaseUrlToHref:d,...u}=e;const p=(0,Q.Z)(a),m=(0,Q.Z)(t),f=(0,Q.Z)(r,{forcePrependBaseUrl:!0}),g=s&&r&&!(0,J.Z)(r),h=c?{dangerouslySetInnerHTML:{__html:c}}:{children:o.createElement(o.Fragment,null,s,g&&o.createElement(te.Z,l&&{width:12,height:12}))};return r?o.createElement(X.Z,(0,i.Z)({href:d?f:r},u,h)):o.createElement(X.Z,(0,i.Z)({to:p,isNavLink:!0},(t||n)&&{isActive:(e,t)=>n?ee(n,t.pathname):t.pathname.startsWith(m)},u,h))}function oe(e){let{className:t,isDropdownItem:n=!1,...r}=e;const s=o.createElement(ne,(0,i.Z)({className:(0,a.Z)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n},r));return n?o.createElement("li",null,s):s}function ae(e){let{className:t,isDropdownItem:n,...r}=e;return o.createElement("li",{className:"menu__list-item"},o.createElement(ne,(0,i.Z)({className:(0,a.Z)("menu__link",t)},r)))}function re(e){let{mobile:t=!1,position:n,...a}=e;const r=t?ae:oe;return o.createElement(r,(0,i.Z)({},a,{activeClassName:a.activeClassName??(t?"menu__link--active":"navbar__link--active")}))}var se=n(86043),ie=n(48596),ce=n(52263);function le(e,t){return e.some((e=>function(e,t){return!!(0,ie.Mg)(e.to,t)||!!ee(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function de(e){let{items:t,position:n,className:r,onClick:s,...c}=e;const l=(0,o.useRef)(null),[d,u]=(0,o.useState)(!1);return(0,o.useEffect)((()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&u(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[l]),o.createElement("div",{ref:l,className:(0,a.Z)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":d})},o.createElement(ne,(0,i.Z)({"aria-haspopup":"true","aria-expanded":d,role:"button",href:c.to?void 0:"#",className:(0,a.Z)("navbar__link",r)},c,{onClick:c.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),u(!d))}}),c.children??c.label),o.createElement("ul",{className:"dropdown__menu"},t.map(((e,t)=>o.createElement(qe,(0,i.Z)({isDropdownItem:!0,activeClassName:"dropdown__link--active"},e,{key:t}))))))}function ue(e){let{items:t,className:n,position:r,onClick:s,...l}=e;const d=function(){const{siteConfig:{baseUrl:e}}=(0,ce.Z)(),{pathname:t}=(0,c.TH)();return t.replace(e,"/")}(),u=le(t,d),{collapsed:p,toggleCollapsed:m,setCollapsed:f}=(0,se.u)({initialState:()=>!u});return(0,o.useEffect)((()=>{u&&f(!u)}),[d,u,f]),o.createElement("li",{className:(0,a.Z)("menu__list-item",{"menu__list-item--collapsed":p})},o.createElement(ne,(0,i.Z)({role:"button",className:(0,a.Z)("menu__link menu__link--sublist menu__link--sublist-caret",n)},l,{onClick:e=>{e.preventDefault(),m()}}),l.children??l.label),o.createElement(se.z,{lazy:!0,as:"ul",className:"menu__list",collapsed:p},t.map(((e,t)=>o.createElement(qe,(0,i.Z)({mobile:!0,isDropdownItem:!0,onClick:s,activeClassName:"menu__link--active"},e,{key:t}))))))}function pe(e){let{mobile:t=!1,...n}=e;const a=t?ue:de;return o.createElement(a,n)}var me=n(94711);function fe(e){let{width:t=20,height:n=20,...a}=e;return o.createElement("svg",(0,i.Z)({viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0},a),o.createElement("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"}))}const ge="iconLanguage_nlXk";function he(){return o.createElement("svg",{width:"15",height:"15",className:"DocSearch-Control-Key-Icon"},o.createElement("path",{d:"M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953",strokeWidth:"1.2",stroke:"currentColor",fill:"none",strokeLinecap:"square"}))}var be=n(20830),ve=["translations"];function xe(){return xe=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,o=new Array(t);n=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var ke="Ctrl";var Se=o.forwardRef((function(e,t){var n=e.translations,a=void 0===n?{}:n,r=we(e,ve),s=a.buttonText,i=void 0===s?"Search":s,c=a.buttonAriaLabel,l=void 0===c?"Search":c,d=ye((0,o.useState)(null),2),u=d[0],p=d[1];return(0,o.useEffect)((function(){"undefined"!=typeof navigator&&(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?p("\u2318"):p(ke))}),[]),o.createElement("button",xe({type:"button",className:"DocSearch DocSearch-Button","aria-label":l},r,{ref:t}),o.createElement("span",{className:"DocSearch-Button-Container"},o.createElement(be.W,null),o.createElement("span",{className:"DocSearch-Button-Placeholder"},i)),o.createElement("span",{className:"DocSearch-Button-Keys"},null!==u&&o.createElement(o.Fragment,null,o.createElement("kbd",{className:"DocSearch-Button-Key"},u===ke?o.createElement(he,null):u),o.createElement("kbd",{className:"DocSearch-Button-Key"},"K"))))})),Ee=n(35742);const Te="q";function Ce(){const{withBaseUrl:e}=(0,Q.C)(),{algolia:{externalUrlRegex:t,replaceSearchResultPathname:n}}=function(){const{siteConfig:{themeConfig:e}}=(0,ce.Z)();return e}();return(0,o.useCallback)((o=>{const a=new URL(o);if(ee(t,a.href))return o;const r=`${a.pathname+a.hash}`;return e(function(e,t){return t?e.replaceAll(new RegExp(t.from,"g"),t.to):e}(r,n))}),[e,t,n])}var Ae=n(43320);var Le=n(73935);const Oe={button:{buttonText:(0,l.I)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"}),buttonAriaLabel:(0,l.I)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"})},modal:{searchBox:{resetButtonTitle:(0,l.I)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),resetButtonAriaLabel:(0,l.I)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),cancelButtonText:(0,l.I)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"}),cancelButtonAriaLabel:(0,l.I)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"})},startScreen:{recentSearchesTitle:(0,l.I)({id:"theme.SearchModal.startScreen.recentSearchesTitle",message:"Recent",description:"The title for recent searches"}),noRecentSearchesText:(0,l.I)({id:"theme.SearchModal.startScreen.noRecentSearchesText",message:"No recent searches",description:"The text when no recent searches"}),saveRecentSearchButtonTitle:(0,l.I)({id:"theme.SearchModal.startScreen.saveRecentSearchButtonTitle",message:"Save this search",description:"The label for save recent search button"}),removeRecentSearchButtonTitle:(0,l.I)({id:"theme.SearchModal.startScreen.removeRecentSearchButtonTitle",message:"Remove this search from history",description:"The label for remove recent search button"}),favoriteSearchesTitle:(0,l.I)({id:"theme.SearchModal.startScreen.favoriteSearchesTitle",message:"Favorite",description:"The title for favorite searches"}),removeFavoriteSearchButtonTitle:(0,l.I)({id:"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle",message:"Remove this search from favorites",description:"The label for remove favorite search button"})},errorScreen:{titleText:(0,l.I)({id:"theme.SearchModal.errorScreen.titleText",message:"Unable to fetch results",description:"The title for error screen of search modal"}),helpText:(0,l.I)({id:"theme.SearchModal.errorScreen.helpText",message:"You might want to check your network connection.",description:"The help text for error screen of search modal"})},footer:{selectText:(0,l.I)({id:"theme.SearchModal.footer.selectText",message:"to select",description:"The explanatory text of the action for the enter key"}),selectKeyAriaLabel:(0,l.I)({id:"theme.SearchModal.footer.selectKeyAriaLabel",message:"Enter key",description:"The ARIA label for the Enter key button that makes the selection"}),navigateText:(0,l.I)({id:"theme.SearchModal.footer.navigateText",message:"to navigate",description:"The explanatory text of the action for the Arrow up and Arrow down key"}),navigateUpKeyAriaLabel:(0,l.I)({id:"theme.SearchModal.footer.navigateUpKeyAriaLabel",message:"Arrow up",description:"The ARIA label for the Arrow up key button that makes the navigation"}),navigateDownKeyAriaLabel:(0,l.I)({id:"theme.SearchModal.footer.navigateDownKeyAriaLabel",message:"Arrow down",description:"The ARIA label for the Arrow down key button that makes the navigation"}),closeText:(0,l.I)({id:"theme.SearchModal.footer.closeText",message:"to close",description:"The explanatory text of the action for Escape key"}),closeKeyAriaLabel:(0,l.I)({id:"theme.SearchModal.footer.closeKeyAriaLabel",message:"Escape key",description:"The ARIA label for the Escape key button that close the modal"}),searchByText:(0,l.I)({id:"theme.SearchModal.footer.searchByText",message:"Search by",description:"The text explain that the search is making by Algolia"})},noResultsScreen:{noResultsText:(0,l.I)({id:"theme.SearchModal.noResultsScreen.noResultsText",message:"No results for",description:"The text explains that there are no results for the following search"}),suggestedQueryText:(0,l.I)({id:"theme.SearchModal.noResultsScreen.suggestedQueryText",message:"Try searching for",description:"The text for the suggested query when no results are found for the following search"}),reportMissingResultsText:(0,l.I)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsText",message:"Believe this query should return results?",description:"The text for the question where the user thinks there are missing results"}),reportMissingResultsLinkText:(0,l.I)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText",message:"Let us know.",description:"The text for the link to report missing results"})}},placeholder:(0,l.I)({id:"theme.SearchModal.placeholder",message:"Search docs",description:"The placeholder of the input of the DocSearch pop-up modal"})};let Ne=null;function Pe(e){let{hit:t,children:n}=e;return o.createElement(X.Z,{to:t.url},n)}function Ie(e){let{state:t,onClose:n}=e;const a=function(){const{siteConfig:{baseUrl:e,themeConfig:t}}=(0,ce.Z)(),{algolia:{searchPagePath:n}}=t;return(0,o.useCallback)((t=>`${e}${n}?${Te}=${encodeURIComponent(t)}`),[e,n])}();return o.createElement(X.Z,{to:a(t.query),onClick:n},o.createElement(l.Z,{id:"theme.SearchBar.seeAll",values:{count:t.context.nbHits}},"See all {count} results"))}function Re(e){let{contextualSearch:t,externalUrlRegex:a,...r}=e;const{siteMetadata:s}=(0,ce.Z)(),l=Ce(),d=function(){const{locale:e,tags:t}=(0,Ae._q)();return[`language:${e}`,t.map((e=>`docusaurus_tag:${e}`))]}(),u=r.searchParameters?.facetFilters??[],p=t?function(e,t){const n=e=>"string"==typeof e?[e]:e;return[...n(e),...n(t)]}(d,u):u,m={...r.searchParameters,facetFilters:p},f=(0,c.k6)(),g=(0,o.useRef)(null),h=(0,o.useRef)(null),[b,v]=(0,o.useState)(!1),[x,y]=(0,o.useState)(void 0),_=(0,o.useCallback)((()=>Ne?Promise.resolve():Promise.all([n.e(1426).then(n.bind(n,61426)),Promise.all([n.e(532),n.e(6945)]).then(n.bind(n,46945)),Promise.all([n.e(532),n.e(8894)]).then(n.bind(n,18894))]).then((e=>{let[{DocSearchModal:t}]=e;Ne=t}))),[]),w=(0,o.useCallback)((()=>{_().then((()=>{g.current=document.createElement("div"),document.body.insertBefore(g.current,document.body.firstChild),v(!0)}))}),[_,v]),k=(0,o.useCallback)((()=>{v(!1),g.current?.remove()}),[v]),S=(0,o.useCallback)((e=>{_().then((()=>{v(!0),y(e.key)}))}),[_,v,y]),E=(0,o.useRef)({navigate(e){let{itemUrl:t}=e;ee(a,t)?window.location.href=t:f.push(t)}}).current,T=(0,o.useRef)((e=>r.transformItems?r.transformItems(e):e.map((e=>({...e,url:l(e.url)}))))).current,C=(0,o.useMemo)((()=>e=>o.createElement(Ie,(0,i.Z)({},e,{onClose:k}))),[k]),A=(0,o.useCallback)((e=>(e.addAlgoliaAgent("docusaurus",s.docusaurusVersion),e)),[s.docusaurusVersion]);return function(e){var t=e.isOpen,n=e.onOpen,a=e.onClose,r=e.onInput,s=e.searchButtonRef;o.useEffect((function(){function e(e){var o;(27===e.keyCode&&t||"k"===(null===(o=e.key)||void 0===o?void 0:o.toLowerCase())&&(e.metaKey||e.ctrlKey)||!function(e){var t=e.target,n=t.tagName;return t.isContentEditable||"INPUT"===n||"SELECT"===n||"TEXTAREA"===n}(e)&&"/"===e.key&&!t)&&(e.preventDefault(),t?a():document.body.classList.contains("DocSearch--active")||document.body.classList.contains("DocSearch--active")||n()),s&&s.current===document.activeElement&&r&&/[a-zA-Z0-9]/.test(String.fromCharCode(e.keyCode))&&r(e)}return window.addEventListener("keydown",e),function(){window.removeEventListener("keydown",e)}}),[t,n,a,r,s])}({isOpen:b,onOpen:w,onClose:k,onInput:S,searchButtonRef:h}),o.createElement(o.Fragment,null,o.createElement(Ee.Z,null,o.createElement("link",{rel:"preconnect",href:`https://${r.appId}-dsn.algolia.net`,crossOrigin:"anonymous"})),o.createElement(Se,{onTouchStart:_,onFocus:_,onMouseOver:_,onClick:w,ref:h,translations:Oe.button}),b&&Ne&&g.current&&(0,Le.createPortal)(o.createElement(Ne,(0,i.Z)({onClose:k,initialScrollY:window.scrollY,initialQuery:x,navigator:E,transformItems:T,hitComponent:Pe,transformSearchClient:A},r.searchPagePath&&{resultsFooterComponent:C},r,{searchParameters:m,placeholder:Oe.placeholder,translations:Oe.modal})),g.current))}function Me(){const{siteConfig:e}=(0,ce.Z)();return o.createElement(Re,e.themeConfig.algolia)}const De={searchBox:"searchBox_ZlJk"};function Be(e){let{children:t,className:n}=e;return o.createElement("div",{className:(0,a.Z)(n,De.searchBox)},t)}var je=n(80143),Fe=n(52802);var ze=n(60373);const $e=e=>e.docs.find((t=>t.id===e.mainDocId));const Ue={default:re,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:a,...r}=e;const{i18n:{currentLocale:s,locales:d,localeConfigs:u}}=(0,ce.Z)(),p=(0,me.l)(),{search:m,hash:f}=(0,c.TH)(),g=[...n,...d.map((e=>{const n=`${`pathname://${p.createUrl({locale:e,fullyQualified:!1})}`}${m}${f}`;return{label:u[e].label,lang:u[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===s?t?"menu__link--active":"dropdown__link--active":""}})),...a],h=t?(0,l.I)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):u[s].label;return o.createElement(pe,(0,i.Z)({},r,{mobile:t,label:o.createElement(o.Fragment,null,o.createElement(fe,{className:ge}),h),items:g}))},search:function(e){let{mobile:t,className:n}=e;return t?null:o.createElement(Be,{className:n},o.createElement(Me,null))},dropdown:pe,html:function(e){let{value:t,className:n,mobile:r=!1,isDropdownItem:s=!1}=e;const i=s?"li":"div";return o.createElement(i,{className:(0,a.Z)({navbar__item:!r&&!s,"menu__list-item":r},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:a,...r}=e;const{activeDoc:s}=(0,je.Iw)(a),c=(0,Fe.vY)(t,a);return null===c?null:o.createElement(re,(0,i.Z)({exact:!0},r,{isActive:()=>s?.path===c.path||!!s?.sidebar&&s.sidebar===c.sidebar,label:n??c.id,to:c.path}))},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:a,...r}=e;const{activeDoc:s}=(0,je.Iw)(a),c=(0,Fe.oz)(t,a).link;if(!c)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return o.createElement(re,(0,i.Z)({exact:!0},r,{isActive:()=>s?.sidebar===t,label:n??c.label,to:c.path}))},docsVersion:function(e){let{label:t,to:n,docsPluginId:a,...r}=e;const s=(0,Fe.lO)(a)[0],c=t??s.label,l=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(s).path;return o.createElement(re,(0,i.Z)({},r,{label:c,to:l}))},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:a,dropdownItemsBefore:r,dropdownItemsAfter:s,...d}=e;const{search:u,hash:p}=(0,c.TH)(),m=(0,je.Iw)(n),f=(0,je.gB)(n),{savePreferredVersionName:g}=(0,ze.J)(n),h=[...r,...f.map((e=>{const t=m.alternateDocVersions[e.name]??$e(e);return{label:e.label,to:`${t.path}${u}${p}`,isActive:()=>e===m.activeVersion,onClick:()=>g(e.name)}})),...s],b=(0,Fe.lO)(n)[0],v=t&&h.length>1?(0,l.I)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):b.label,x=t&&h.length>1?void 0:$e(b).path;return h.length<=1?o.createElement(re,(0,i.Z)({},d,{mobile:t,label:v,to:x,isActive:a?()=>!1:void 0})):o.createElement(pe,(0,i.Z)({},d,{mobile:t,label:v,to:x,items:h,isActive:a?()=>!1:void 0}))}};function qe(e){let{type:t,...n}=e;const a=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),r=Ue[a];if(!r)throw new Error(`No NavbarItem component found for type "${t}".`);return o.createElement(r,n)}function He(){const e=(0,L.e)(),t=(0,y.L)().navbar.items;return o.createElement("ul",{className:"menu__list"},t.map(((t,n)=>o.createElement(qe,(0,i.Z)({mobile:!0},t,{onClick:()=>e.toggle(),key:n})))))}function Ze(e){return o.createElement("button",(0,i.Z)({},e,{type:"button",className:"clean-btn navbar-sidebar__back"}),o.createElement(l.Z,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"},"\u2190 Back to main menu"))}function Ve(){const e=0===(0,y.L)().navbar.items.length,t=D();return o.createElement(o.Fragment,null,!e&&o.createElement(Ze,{onClick:()=>t.hide()}),t.content)}function Ge(){const e=(0,L.e)();var t;return void 0===(t=e.shown)&&(t=!0),(0,o.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?o.createElement(B,{header:o.createElement(K,null),primaryMenu:o.createElement(He,null),secondaryMenu:o.createElement(Ve,null)}):null}const We={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Ye(e){return o.createElement("div",(0,i.Z)({role:"presentation"},e,{className:(0,a.Z)("navbar-sidebar__backdrop",e.className)}))}function Ke(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:r}}=(0,y.L)(),s=(0,L.e)(),{navbarRef:i,isNavbarVisible:c}=function(e){const[t,n]=(0,o.useState)(e),a=(0,o.useRef)(!1),r=(0,o.useRef)(0),s=(0,o.useCallback)((e=>{null!==e&&(r.current=e.getBoundingClientRect().height)}),[]);return(0,O.RF)(((t,o)=>{let{scrollY:s}=t;if(!e)return;if(s=i?n(!1):s+l{if(!e)return;const o=t.location.hash;if(o?document.getElementById(o.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:s,isNavbarVisible:t}}(n);return o.createElement("nav",{ref:i,"aria-label":(0,l.I)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.Z)("navbar","navbar--fixed-top",n&&[We.navbarHideable,!c&&We.navbarHidden],{"navbar--dark":"dark"===r,"navbar--primary":"primary"===r,"navbar-sidebar--show":s.shown})},t,o.createElement(Ye,{onClick:s.toggle}),o.createElement(Ge,null))}var Xe=n(18780);const Qe={errorBoundaryError:"errorBoundaryError_a6uf"};function Je(e){return o.createElement("button",(0,i.Z)({type:"button"},e),o.createElement(l.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error"},"Try again"))}function et(e){let{error:t}=e;const n=(0,Xe.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return o.createElement("p",{className:Qe.errorBoundaryError},n)}class tt extends o.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const nt="right";function ot(e){let{width:t=30,height:n=30,className:a,...r}=e;return o.createElement("svg",(0,i.Z)({className:a,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true"},r),o.createElement("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"}))}function at(){const{toggle:e,shown:t}=(0,L.e)();return o.createElement("button",{onClick:e,"aria-label":(0,l.I)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button"},o.createElement(ot,null))}const rt={colorModeToggle:"colorModeToggle_DEke"};function st(e){let{items:t}=e;return o.createElement(o.Fragment,null,t.map(((e,t)=>o.createElement(tt,{key:t,onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t})},o.createElement(qe,e)))))}function it(e){let{left:t,right:n}=e;return o.createElement("div",{className:"navbar__inner"},o.createElement("div",{className:"navbar__items"},t),o.createElement("div",{className:"navbar__items navbar__items--right"},n))}function ct(){const e=(0,L.e)(),t=(0,y.L)().navbar.items,[n,a]=function(e){function t(e){return"left"===(e.position??nt)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),r=t.find((e=>"search"===e.type));return o.createElement(it,{left:o.createElement(o.Fragment,null,!e.disabled&&o.createElement(at,null),o.createElement(W,null),o.createElement(st,{items:n})),right:o.createElement(o.Fragment,null,o.createElement(st,{items:a}),o.createElement(V,{className:rt.colorModeToggle}),!r&&o.createElement(Be,null,o.createElement(Me,null)))})}function lt(){return o.createElement(Ke,null,o.createElement(ct,null))}function dt(e){let{item:t}=e;const{to:n,href:a,label:r,prependBaseUrlToHref:s,...c}=t,l=(0,Q.Z)(n),d=(0,Q.Z)(a,{forcePrependBaseUrl:!0});return o.createElement(X.Z,(0,i.Z)({className:"footer__link-item"},a?{href:s?d:a}:{to:l},c),r,a&&!(0,J.Z)(a)&&o.createElement(te.Z,null))}function ut(e){let{item:t}=e;return t.html?o.createElement("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):o.createElement("li",{key:t.href??t.to,className:"footer__item"},o.createElement(dt,{item:t}))}function pt(e){let{column:t}=e;return o.createElement("div",{className:"col footer__col"},o.createElement("div",{className:"footer__title"},t.title),o.createElement("ul",{className:"footer__items clean-list"},t.items.map(((e,t)=>o.createElement(ut,{key:t,item:e})))))}function mt(e){let{columns:t}=e;return o.createElement("div",{className:"row footer__links"},t.map(((e,t)=>o.createElement(pt,{key:t,column:e}))))}function ft(){return o.createElement("span",{className:"footer__link-separator"},"\xb7")}function gt(e){let{item:t}=e;return t.html?o.createElement("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):o.createElement(dt,{item:t})}function ht(e){let{links:t}=e;return o.createElement("div",{className:"footer__links text--center"},o.createElement("div",{className:"footer__links"},t.map(((e,n)=>o.createElement(o.Fragment,{key:n},o.createElement(gt,{item:e}),t.length!==n+1&&o.createElement(ft,null))))))}function bt(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?o.createElement(mt,{columns:t}):o.createElement(ht,{links:t})}var vt=n(50941);const xt={footerLogoLink:"footerLogoLink_BH7S"};function yt(e){let{logo:t}=e;const{withBaseUrl:n}=(0,Q.C)(),r={light:n(t.src),dark:n(t.srcDark??t.src)};return o.createElement(vt.Z,{className:(0,a.Z)("footer__logo",t.className),alt:t.alt,sources:r,width:t.width,height:t.height,style:t.style})}function _t(e){let{logo:t}=e;return t.href?o.createElement(X.Z,{href:t.href,className:xt.footerLogoLink,target:t.target},o.createElement(yt,{logo:t})):o.createElement(yt,{logo:t})}function wt(e){let{copyright:t}=e;return o.createElement("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function kt(e){let{style:t,links:n,logo:r,copyright:s}=e;return o.createElement("footer",{className:(0,a.Z)("footer",{"footer--dark":"dark"===t})},o.createElement("div",{className:"container container-fluid"},n,(r||s)&&o.createElement("div",{className:"footer__bottom text--center"},r&&o.createElement("div",{className:"margin-bottom--sm"},r),s)))}function St(){const{footer:e}=(0,y.L)();if(!e)return null;const{copyright:t,links:n,logo:a,style:r}=e;return o.createElement(kt,{style:r,links:n&&n.length>0&&o.createElement(bt,{links:n}),logo:a&&o.createElement(_t,{logo:a}),copyright:t&&o.createElement(wt,{copyright:t})})}const Et=o.memo(St),Tt=(0,N.Qc)([j.S,_.pl,O.OC,ze.L5,s.VC,function(e){let{children:t}=e;return o.createElement(P.n2,null,o.createElement(L.M,null,o.createElement(R,null,t)))}]);function Ct(e){let{children:t}=e;return o.createElement(Tt,null,t)}function At(e){let{error:t,tryAgain:n}=e;return o.createElement("main",{className:"container margin-vert--xl"},o.createElement("div",{className:"row"},o.createElement("div",{className:"col col--6 col--offset-3"},o.createElement("h1",{className:"hero__title"},o.createElement(l.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),o.createElement("div",{className:"margin-vert--lg"},o.createElement(Je,{onClick:n,className:"button button--primary shadow--lw"})),o.createElement("hr",null),o.createElement("div",{className:"margin-vert--md"},o.createElement(et,{error:t})))))}const Lt={mainWrapper:"mainWrapper_z2l0"};function Ot(e){const{children:t,noFooter:n,wrapperClassName:i,title:c,description:l}=e;return(0,b.t)(),o.createElement(Ct,null,o.createElement(s.d,{title:c,description:l}),o.createElement(x,null),o.createElement(A,null),o.createElement(lt,null),o.createElement("div",{id:u,className:(0,a.Z)(h.k.wrapper.main,Lt.mainWrapper,i)},o.createElement(r.Z,{fallback:e=>o.createElement(At,e)},t)),!n&&o.createElement(Et,null))}},21327:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var o=n(87462),a=n(67294),r=n(39960),s=n(44996),i=n(52263),c=n(86668),l=n(50941);function d(e){let{logo:t,alt:n,imageClassName:o}=e;const r={light:(0,s.Z)(t.src),dark:(0,s.Z)(t.srcDark||t.src)},i=a.createElement(l.Z,{className:t.className,sources:r,height:t.height,width:t.width,alt:n,style:t.style});return o?a.createElement("div",{className:o},i):i}function u(e){const{siteConfig:{title:t}}=(0,i.Z)(),{navbar:{title:n,logo:l}}=(0,c.L)(),{imageClassName:u,titleClassName:p,...m}=e,f=(0,s.Z)(l?.href||"/"),g=n?"":t,h=l?.alt??g;return a.createElement(r.Z,(0,o.Z)({to:f},m,l?.target&&{target:l.target}),l&&a.createElement(d,{logo:l,alt:h,imageClassName:u}),null!=n&&a.createElement("b",{className:p},n))}},90197:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});var o=n(67294),a=n(35742);function r(e){let{locale:t,version:n,tag:r}=e;const s=t;return o.createElement(a.Z,null,t&&o.createElement("meta",{name:"docusaurus_locale",content:t}),n&&o.createElement("meta",{name:"docusaurus_version",content:n}),r&&o.createElement("meta",{name:"docusaurus_tag",content:r}),s&&o.createElement("meta",{name:"docsearch:language",content:s}),n&&o.createElement("meta",{name:"docsearch:version",content:n}),r&&o.createElement("meta",{name:"docsearch:docusaurus_tag",content:r}))}},50941:(e,t,n)=>{"use strict";n.d(t,{Z:()=>l});var o=n(87462),a=n(67294),r=n(86010),s=n(72389),i=n(92949);const c={themedImage:"themedImage_ToTc","themedImage--light":"themedImage--light_HNdA","themedImage--dark":"themedImage--dark_i4oU"};function l(e){const t=(0,s.Z)(),{colorMode:n}=(0,i.I)(),{sources:l,className:d,alt:u,...p}=e,m=t?"dark"===n?["dark"]:["light"]:["light","dark"];return a.createElement(a.Fragment,null,m.map((e=>a.createElement("img",(0,o.Z)({key:e,src:l[e],alt:u,className:(0,r.Z)(c.themedImage,c[`themedImage--${e}`],d)},p)))))}},86043:(e,t,n)=>{"use strict";n.d(t,{u:()=>c,z:()=>h});var o=n(87462),a=n(67294),r=n(10412),s=n(91442);const i="ease-in-out";function c(e){let{initialState:t}=e;const[n,o]=(0,a.useState)(t??!1),r=(0,a.useCallback)((()=>{o((e=>!e))}),[]);return{collapsed:n,setCollapsed:o,toggleCollapsed:r}}const l={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function u(e,t){const n=t?l:d;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function p(e){let{collapsibleRef:t,collapsed:n,animation:o}=e;const r=(0,a.useRef)(!1);(0,a.useEffect)((()=>{const e=t.current;function a(){const t=e.scrollHeight,n=o?.duration??function(e){if((0,s.n)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${o?.easing??i}`,height:`${t}px`}}function c(){const t=a();e.style.transition=t.transition,e.style.height=t.height}if(!r.current)return u(e,n),void(r.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(c(),requestAnimationFrame((()=>{e.style.height=l.height,e.style.overflow=l.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{c()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,o])}function m(e){if(!r.Z.canUseDOM)return e?l:d}function f(e){let{as:t="div",collapsed:n,children:o,animation:r,onCollapseTransitionEnd:s,className:i,disableSSRStyle:c}=e;const l=(0,a.useRef)(null);return p({collapsibleRef:l,collapsed:n,animation:r}),a.createElement(t,{ref:l,style:c?void 0:m(n),onTransitionEnd:e=>{"height"===e.propertyName&&(u(l.current,n),s?.(n))},className:i},o)}function g(e){let{collapsed:t,...n}=e;const[r,s]=(0,a.useState)(!t),[i,c]=(0,a.useState)(t);return(0,a.useLayoutEffect)((()=>{t||s(!0)}),[t]),(0,a.useLayoutEffect)((()=>{r&&c(t)}),[r,t]),r?a.createElement(f,(0,o.Z)({},n,{collapsed:i})):null}function h(e){let{lazy:t,...n}=e;const o=t?g:f;return a.createElement(o,n)}},59689:(e,t,n)=>{"use strict";n.d(t,{nT:()=>f,pl:()=>m});var o=n(67294),a=n(72389),r=n(50012),s=n(902),i=n(86668);const c=(0,r.WA)("docusaurus.announcement.dismiss"),l=(0,r.WA)("docusaurus.announcement.id"),d=()=>"true"===c.get(),u=e=>c.set(String(e)),p=o.createContext(null);function m(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,i.L)(),t=(0,a.Z)(),[n,r]=(0,o.useState)((()=>!!t&&d()));(0,o.useEffect)((()=>{r(d())}),[]);const s=(0,o.useCallback)((()=>{u(!0),r(!0)}),[]);return(0,o.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=l.get();"annoucement-bar"===n&&(n="announcement-bar");const o=t!==n;l.set(t),o&&u(!1),!o&&d()||r(!1)}),[e]),(0,o.useMemo)((()=>({isActive:!!e&&!n,close:s})),[e,n,s])}();return o.createElement(p.Provider,{value:n},t)}function f(){const e=(0,o.useContext)(p);if(!e)throw new s.i6("AnnouncementBarProvider");return e}},92949:(e,t,n)=>{"use strict";n.d(t,{I:()=>h,S:()=>g});var o=n(67294),a=n(10412),r=n(902),s=n(50012),i=n(86668);const c=o.createContext(void 0),l="theme",d=(0,s.WA)(l),u={light:"light",dark:"dark"},p=e=>e===u.dark?u.dark:u.light,m=e=>a.Z.canUseDOM?p(document.documentElement.getAttribute("data-theme")):p(e),f=e=>{d.set(p(e))};function g(e){let{children:t}=e;const n=function(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,i.L)(),[a,r]=(0,o.useState)(m(e));(0,o.useEffect)((()=>{t&&d.del()}),[t]);const s=(0,o.useCallback)((function(t,o){void 0===o&&(o={});const{persist:a=!0}=o;t?(r(t),a&&f(t)):(r(n?window.matchMedia("(prefers-color-scheme: dark)").matches?u.dark:u.light:e),d.del())}),[n,e]);(0,o.useEffect)((()=>{document.documentElement.setAttribute("data-theme",p(a))}),[a]),(0,o.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==l)return;const t=d.get();null!==t&&s(p(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,s]);const c=(0,o.useRef)(!1);return(0,o.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),o=()=>{window.matchMedia("print").matches||c.current?c.current=window.matchMedia("print").matches:s(null)};return e.addListener(o),()=>e.removeListener(o)}),[s,t,n]),(0,o.useMemo)((()=>({colorMode:a,setColorMode:s,get isDarkTheme(){return a===u.dark},setLightTheme(){s(u.light)},setDarkTheme(){s(u.dark)}})),[a,s])}();return o.createElement(c.Provider,{value:n},t)}function h(){const e=(0,o.useContext)(c);if(null==e)throw new r.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},60373:(e,t,n)=>{"use strict";n.d(t,{J:()=>v,L5:()=>h,Oh:()=>x});var o=n(67294),a=n(80143),r=n(29935),s=n(86668),i=n(52802),c=n(902),l=n(50012);const d=e=>`docs-preferred-version-${e}`,u={save:(e,t,n)=>{(0,l.WA)(d(e),{persistence:t}).set(n)},read:(e,t)=>(0,l.WA)(d(e),{persistence:t}).get(),clear:(e,t)=>{(0,l.WA)(d(e),{persistence:t}).del()}},p=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const m=o.createContext(null);function f(){const e=(0,a._r)(),t=(0,s.L)().docs.versionPersistence,n=(0,o.useMemo)((()=>Object.keys(e)),[e]),[r,i]=(0,o.useState)((()=>p(n)));(0,o.useEffect)((()=>{i(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:o}=e;function a(e){const t=u.read(e,n);return o[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(u.clear(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,a(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[r,(0,o.useMemo)((()=>({savePreferredVersion:function(e,n){u.save(e,t,n),i((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function g(e){let{children:t}=e;const n=f();return o.createElement(m.Provider,{value:n},t)}function h(e){let{children:t}=e;return i.cE?o.createElement(g,null,t):o.createElement(o.Fragment,null,t)}function b(){const e=(0,o.useContext)(m);if(!e)throw new c.i6("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=r.m);const t=(0,a.zh)(e),[n,s]=b(),{preferredVersionName:i}=n[e];return{preferredVersion:t.versions.find((e=>e.name===i))??null,savePreferredVersionName:(0,o.useCallback)((t=>{s.savePreferredVersion(e,t)}),[s,e])}}function x(){const e=(0,a._r)(),[t]=b();function n(n){const o=e[n],{preferredVersionName:a}=t[n];return o.versions.find((e=>e.name===a))??null}const o=Object.keys(e);return Object.fromEntries(o.map((e=>[e,n(e)])))}},1116:(e,t,n)=>{"use strict";n.d(t,{V:()=>c,b:()=>i});var o=n(67294),a=n(902);const r=Symbol("EmptyContext"),s=o.createContext(r);function i(e){let{children:t,name:n,items:a}=e;const r=(0,o.useMemo)((()=>n&&a?{name:n,items:a}:null),[n,a]);return o.createElement(s.Provider,{value:r},t)}function c(){const e=(0,o.useContext)(s);if(e===r)throw new a.i6("DocsSidebarProvider");return e}},74477:(e,t,n)=>{"use strict";n.d(t,{E:()=>i,q:()=>s});var o=n(67294),a=n(902);const r=o.createContext(null);function s(e){let{children:t,version:n}=e;return o.createElement(r.Provider,{value:n},t)}function i(){const e=(0,o.useContext)(r);if(null===e)throw new a.i6("DocsVersionProvider");return e}},72961:(e,t,n)=>{"use strict";n.d(t,{M:()=>p,e:()=>m});var o=n(67294),a=n(13102),r=n(87524),s=n(16550),i=(n(61688),n(902));function c(e){!function(e){const t=(0,s.k6)(),n=(0,i.zX)(e);(0,o.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var l=n(86668);const d=o.createContext(void 0);function u(){const e=function(){const e=(0,a.HY)(),{items:t}=(0,l.L)().navbar;return 0===t.length&&!e.component}(),t=(0,r.i)(),n=!e&&"mobile"===t,[s,i]=(0,o.useState)(!1);c((()=>{if(s)return i(!1),!1}));const d=(0,o.useCallback)((()=>{i((e=>!e))}),[]);return(0,o.useEffect)((()=>{"desktop"===t&&i(!1)}),[t]),(0,o.useMemo)((()=>({disabled:e,shouldRender:n,toggle:d,shown:s})),[e,n,d,s])}function p(e){let{children:t}=e;const n=u();return o.createElement(d.Provider,{value:n},t)}function m(){const e=o.useContext(d);if(void 0===e)throw new i.i6("NavbarMobileSidebarProvider");return e}},13102:(e,t,n)=>{"use strict";n.d(t,{HY:()=>i,Zo:()=>c,n2:()=>s});var o=n(67294),a=n(902);const r=o.createContext(null);function s(e){let{children:t}=e;const n=(0,o.useState)({component:null,props:null});return o.createElement(r.Provider,{value:n},t)}function i(){const e=(0,o.useContext)(r);if(!e)throw new a.i6("NavbarSecondaryMenuContentProvider");return e[0]}function c(e){let{component:t,props:n}=e;const s=(0,o.useContext)(r);if(!s)throw new a.i6("NavbarSecondaryMenuContentProvider");const[,i]=s,c=(0,a.Ql)(n);return(0,o.useEffect)((()=>{i({component:t,props:c})}),[i,t,c]),(0,o.useEffect)((()=>()=>i({component:null,props:null})),[i]),null}},19727:(e,t,n)=>{"use strict";n.d(t,{h:()=>a,t:()=>r});var o=n(67294);const a="navigation-with-keyboard";function r(){(0,o.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},87524:(e,t,n)=>{"use strict";n.d(t,{i:()=>l});var o=n(67294),a=n(10412);const r={desktop:"desktop",mobile:"mobile",ssr:"ssr"},s=996;function i(){return a.Z.canUseDOM?window.innerWidth>s?r.desktop:r.mobile:r.ssr}const c=!1;function l(){const[e,t]=(0,o.useState)((()=>c?"ssr":i()));return(0,o.useEffect)((()=>{function e(){t(i())}const n=c?window.setTimeout(e,1e3):void 0;return window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e),clearTimeout(n)}}),[]),e}},35281:(e,t,n)=>{"use strict";n.d(t,{k:()=>o});const o={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},91442:(e,t,n)=>{"use strict";function o(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{n:()=>o})},52802:(e,t,n)=>{"use strict";n.d(t,{MN:()=>E,Wl:()=>f,_F:()=>v,cE:()=>p,jA:()=>g,xz:()=>m,hI:()=>S,lO:()=>_,vY:()=>k,oz:()=>w,s1:()=>y});var o=n(67294),a=n(16550),r=n(18790),s=n(80143),i=n(60373),c=n(74477),l=n(1116);function d(e){return Array.from(new Set(e))}var u=n(48596);const p=!!s._r;function m(e){const t=(0,c.E)();if(!e)return;const n=t.docs[e];if(!n)throw new Error(`no version doc found by id=${e}`);return n}function f(e){if(e.href)return e.href;for(const t of e.items){if("link"===t.type)return t.href;if("category"===t.type){const e=f(t);if(e)return e}}}function g(){const{pathname:e}=(0,a.TH)(),t=(0,l.V)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");const n=x({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw new Error(`${e} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`);return n}const h=(e,t)=>void 0!==e&&(0,u.Mg)(e,t),b=(e,t)=>e.some((e=>v(e,t)));function v(e,t){return"link"===e.type?h(e.href,t):"category"===e.type&&(h(e.href,t)||b(e.items,t))}function x(e){let{sidebarItems:t,pathname:n,onlyCategories:o=!1}=e;const a=[];return function e(t){for(const r of t)if("category"===r.type&&((0,u.Mg)(r.href,n)||e(r.items))||"link"===r.type&&(0,u.Mg)(r.href,n)){return o&&"category"!==r.type||a.unshift(r),!0}return!1}(t),a}function y(){const e=(0,l.V)(),{pathname:t}=(0,a.TH)(),n=(0,s.gA)()?.pluginData.breadcrumbs;return!1!==n&&e?x({sidebarItems:e.items,pathname:t}):null}function _(e){const{activeVersion:t}=(0,s.Iw)(e),{preferredVersion:n}=(0,i.J)(e),a=(0,s.yW)(e);return(0,o.useMemo)((()=>d([t,n,a].filter(Boolean))),[t,n,a])}function w(e,t){const n=_(t);return(0,o.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),o=t.find((t=>t[0]===e));if(!o)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return o[1]}),[e,n])}function k(e,t){const n=_(t);return(0,o.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),o=t.find((t=>t.id===e));if(!o){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${d(t.map((e=>e.id))).join("\n- ")}`)}return o}),[e,n])}function S(e){let{route:t,versionMetadata:n}=e;const o=(0,a.TH)(),s=t.routes,i=s.find((e=>(0,a.LX)(o.pathname,e)));if(!i)return null;const c=i.sidebar,l=c?n.docsSidebars[c]:void 0;return{docElement:(0,r.H)(s),sidebarName:c,sidebarItems:l}}function E(e){return e.filter((e=>"category"!==e.type||!!f(e)))}},1944:(e,t,n)=>{"use strict";n.d(t,{FG:()=>p,d:()=>d,VC:()=>m});var o=n(67294),a=n(86010),r=n(35742),s=n(30226);function i(){const e=o.useContext(s._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var c=n(44996),l=n(52263);function d(e){let{title:t,description:n,keywords:a,image:s,children:i}=e;const d=function(e){const{siteConfig:t}=(0,l.Z)(),{title:n,titleDelimiter:o}=t;return e?.trim().length?`${e.trim()} ${o} ${n}`:n}(t),{withBaseUrl:u}=(0,c.C)(),p=s?u(s,{absolute:!0}):void 0;return o.createElement(r.Z,null,t&&o.createElement("title",null,d),t&&o.createElement("meta",{property:"og:title",content:d}),n&&o.createElement("meta",{name:"description",content:n}),n&&o.createElement("meta",{property:"og:description",content:n}),a&&o.createElement("meta",{name:"keywords",content:Array.isArray(a)?a.join(","):a}),p&&o.createElement("meta",{property:"og:image",content:p}),p&&o.createElement("meta",{name:"twitter:image",content:p}),i)}const u=o.createContext(void 0);function p(e){let{className:t,children:n}=e;const s=o.useContext(u),i=(0,a.Z)(s,t);return o.createElement(u.Provider,{value:i},o.createElement(r.Z,null,o.createElement("html",{className:i})),n)}function m(e){let{children:t}=e;const n=i(),r=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const s=`plugin-id-${n.plugin.id}`;return o.createElement(p,{className:(0,a.Z)(r,s)},t)}},902:(e,t,n)=>{"use strict";n.d(t,{D9:()=>s,Qc:()=>l,Ql:()=>c,i6:()=>i,zX:()=>r});var o=n(67294);const a=n(10412).Z.canUseDOM?o.useLayoutEffect:o.useEffect;function r(e){const t=(0,o.useRef)(e);return a((()=>{t.current=e}),[e]),(0,o.useCallback)((function(){return t.current(...arguments)}),[])}function s(e){const t=(0,o.useRef)();return a((()=>{t.current=e})),t.current}class i extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,o.useMemo)((()=>e),t.flat())}function l(e){return t=>{let{children:n}=t;return o.createElement(o.Fragment,null,e.reduceRight(((e,t)=>o.createElement(t,null,e)),n))}}},48596:(e,t,n)=>{"use strict";n.d(t,{Mg:()=>s,Ns:()=>i});var o=n(67294),a=n(723),r=n(52263);function s(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function i(){const{baseUrl:e}=(0,r.Z)().siteConfig;return(0,o.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function o(e){return e.path===t&&!0===e.exact}function a(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(o)||e(t.filter(a).flatMap((e=>e.routes??[])))}(n)}({routes:a.Z,baseUrl:e})),[e])}},12466:(e,t,n)=>{"use strict";n.d(t,{Ct:()=>p,OC:()=>c,RF:()=>u});var o=n(67294),a=n(10412),r=n(72389),s=n(902);const i=o.createContext(void 0);function c(e){let{children:t}=e;const n=function(){const e=(0,o.useRef)(!0);return(0,o.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return o.createElement(i.Provider,{value:n},t)}function l(){const e=(0,o.useContext)(i);if(null==e)throw new s.i6("ScrollControllerProvider");return e}const d=()=>a.Z.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function u(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=l(),a=(0,o.useRef)(d()),r=(0,s.zX)(e);(0,o.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=d();r(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[r,n,...t])}function p(){const e=(0,o.useRef)(null),t=(0,r.Z)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function o(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&at&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},43320:(e,t,n)=>{"use strict";n.d(t,{HX:()=>s,_q:()=>c,os:()=>i});var o=n(80143),a=n(52263),r=n(60373);const s="default";function i(e,t){return`docs-${e}-${t}`}function c(){const{i18n:e}=(0,a.Z)(),t=(0,o._r)(),n=(0,o.WS)(),c=(0,r.Oh)();const l=[s,...Object.keys(t).map((function(e){const o=n?.activePlugin.pluginId===e?n.activeVersion:void 0,a=c[e],r=t[e].versions.find((e=>e.isLast));return i(e,(o??a??r).name)}))];return{locale:e.currentLocale,tags:l}}},50012:(e,t,n)=>{"use strict";n.d(t,{WA:()=>c});n(67294),n(61688);const o="localStorage";function a(e){let{key:t,oldValue:n,newValue:o,storage:a}=e;if(n===o)return;const r=document.createEvent("StorageEvent");r.initStorageEvent("storage",!1,!1,t,n,o,window.location.href,a),window.dispatchEvent(r)}function r(e){if(void 0===e&&(e=o),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,s||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),s=!0),null}var t}let s=!1;const i={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function c(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=r(t?.persistence);return null===n?i:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const o=n.getItem(e);n.setItem(e,t),a({key:e,oldValue:o,newValue:t,storage:n})}catch(o){console.error(`Docusaurus storage error, can't set ${e}=${t}`,o)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),a({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const o=o=>{o.storageArea===n&&o.key===e&&t(o)};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)}catch(o){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,o),()=>{}}}}}},94711:(e,t,n)=>{"use strict";n.d(t,{l:()=>s});var o=n(52263),a=n(16550),r=n(18780);function s(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:s,currentLocale:i}}=(0,o.Z)(),{pathname:c}=(0,a.TH)(),l=(0,r.applyTrailingSlash)(c,{trailingSlash:n,baseUrl:e}),d=i===s?e:e.replace(`/${i}/`,"/"),u=l.replace(e,"");return{createUrl:function(e){let{locale:n,fullyQualified:o}=e;return`${o?t:""}${function(e){return e===s?`${d}`:`${d}${e}/`}(n)}${u}`}}}},85936:(e,t,n)=>{"use strict";n.d(t,{S:()=>s});var o=n(67294),a=n(16550),r=n(902);function s(e){const t=(0,a.TH)(),n=(0,r.D9)(t),s=(0,r.zX)(e);(0,o.useEffect)((()=>{n&&t!==n&&s({location:t,previousLocation:n})}),[s,t,n])}},86668:(e,t,n)=>{"use strict";n.d(t,{L:()=>a});var o=n(52263);function a(){return(0,o.Z)().siteConfig.themeConfig}},8802:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:o}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[a]=e.split(/[#?]/),r="/"===a||a===o?a:(s=a,n?function(e){return e.endsWith("/")?e:`${e}/`}(s):function(e){return e.endsWith("/")?e.slice(0,-1):e}(s));var s;return e.replace(a,r)}},54143:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},18780:function(e,t,n){"use strict";var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="__blog-post-container";var a=n(8802);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return o(a).default}});var r=n(54143);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return r.getErrorCausalChain}})},86010:(e,t,n)=>{"use strict";function o(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;ta});const a=function(){for(var e,t,n=0,a="";n{"use strict";n.d(t,{lX:()=>y,q_:()=>T,ob:()=>m,PP:()=>A,Ep:()=>p});var o=n(87462);function a(e){return"/"===e.charAt(0)}function r(e,t){for(var n=t,o=n+1,a=e.length;o=0;p--){var m=s[p];"."===m?r(s,p):".."===m?(r(s,p),u++):u&&(r(s,p),u--)}if(!l)for(;u--;u)s.unshift("..");!l||""===s[0]||s[0]&&a(s[0])||s.unshift("");var f=s.join("/");return n&&"/"!==f.substr(-1)&&(f+="/"),f};var i=n(38776);function c(e){return"/"===e.charAt(0)?e:"/"+e}function l(e){return"/"===e.charAt(0)?e.substr(1):e}function d(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function u(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var t=e.pathname,n=e.search,o=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),o&&"#"!==o&&(a+="#"===o.charAt(0)?o:"#"+o),a}function m(e,t,n,a){var r;"string"==typeof e?(r=function(e){var t=e||"/",n="",o="",a=t.indexOf("#");-1!==a&&(o=t.substr(a),t=t.substr(0,a));var r=t.indexOf("?");return-1!==r&&(n=t.substr(r),t=t.substr(0,r)),{pathname:t,search:"?"===n?"":n,hash:"#"===o?"":o}}(e),r.state=t):(void 0===(r=(0,o.Z)({},e)).pathname&&(r.pathname=""),r.search?"?"!==r.search.charAt(0)&&(r.search="?"+r.search):r.search="",r.hash?"#"!==r.hash.charAt(0)&&(r.hash="#"+r.hash):r.hash="",void 0!==t&&void 0===r.state&&(r.state=t));try{r.pathname=decodeURI(r.pathname)}catch(i){throw i instanceof URIError?new URIError('Pathname "'+r.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):i}return n&&(r.key=n),a?r.pathname?"/"!==r.pathname.charAt(0)&&(r.pathname=s(r.pathname,a.pathname)):r.pathname=a.pathname:r.pathname||(r.pathname="/"),r}function f(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,o,a){if(null!=e){var r="function"==typeof e?e(t,n):e;"string"==typeof r?"function"==typeof o?o(r,a):a(!0):a(!1!==r)}else a(!0)},appendListener:function(e){var n=!0;function o(){n&&e.apply(void 0,arguments)}return t.push(o),function(){n=!1,t=t.filter((function(e){return e!==o}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),o=0;ot?n.splice(t,n.length-t,a):n.push(a),u({action:o,location:a,index:t,entries:n})}}))},replace:function(e,t){var o="REPLACE",a=m(e,t,g(),y.location);d.confirmTransitionTo(a,o,n,(function(e){e&&(y.entries[y.index]=a,u({action:o,location:a}))}))},go:x,goBack:function(){x(-1)},goForward:function(){x(1)},canGo:function(e){var t=y.index+e;return t>=0&&t{"use strict";var o=n(59864),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},r={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},s={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function c(e){return o.isMemo(e)?s:i[e.$$typeof]||a}i[o.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[o.Memo]=s;var l=Object.defineProperty,d=Object.getOwnPropertyNames,u=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,m=Object.getPrototypeOf,f=Object.prototype;e.exports=function e(t,n,o){if("string"!=typeof n){if(f){var a=m(n);a&&a!==f&&e(t,a,o)}var s=d(n);u&&(s=s.concat(u(n)));for(var i=c(t),g=c(n),h=0;h{"use strict";e.exports=function(e,t,n,o,a,r,s,i){if(!e){var c;if(void 0===t)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,o,a,r,s,i],d=0;(c=new Error(t.replace(/%s/g,(function(){return l[d++]})))).name="Invariant Violation"}throw c.framesToPop=1,c}}},5826:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},32497:(e,t,n)=>{"use strict";n.r(t)},52295:(e,t,n)=>{"use strict";n.r(t)},74865:function(e,t,n){var o,a;o=function(){var e,t,n={version:"0.2.0"},o=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function a(e,t,n){return en?n:e}function r(e){return 100*(-1+e)}function s(e,t,n){var a;return(a="translate3d"===o.positionUsing?{transform:"translate3d("+r(e)+"%,0,0)"}:"translate"===o.positionUsing?{transform:"translate("+r(e)+"%,0)"}:{"margin-left":r(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(o[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,o.minimum,1),n.status=1===e?null:e;var r=n.render(!t),l=r.querySelector(o.barSelector),d=o.speed,u=o.easing;return r.offsetWidth,i((function(t){""===o.positionUsing&&(o.positionUsing=n.getPositioningCSS()),c(l,s(e,d,u)),1===e?(c(r,{transition:"none",opacity:1}),r.offsetWidth,setTimeout((function(){c(r,{transition:"all "+d+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),d)}),d)):setTimeout(t,d)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),o.trickleSpeed)};return o.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*o.trickleRate)},e=0,t=0,n.promise=function(o){return o&&"resolved"!==o.state()?(0===t&&n.start(),e++,t++,o.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");d(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=o.template;var a,s=t.querySelector(o.barSelector),i=e?"-100":r(n.status||0),l=document.querySelector(o.parent);return c(s,{transition:"all 0 linear",transform:"translate3d("+i+"%,0,0)"}),o.showSpinner||(a=t.querySelector(o.spinnerSelector))&&m(a),l!=document.body&&d(l,"nprogress-custom-parent"),l.appendChild(t),t},n.remove=function(){u(document.documentElement,"nprogress-busy"),u(document.querySelector(o.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&m(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var i=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),c=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function o(t){var n=document.body.style;if(t in n)return t;for(var o,a=e.length,r=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((o=e[a]+r)in n)return o;return t}function a(e){return e=n(e),t[e]||(t[e]=o(e))}function r(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,o,a=arguments;if(2==a.length)for(n in t)void 0!==(o=t[n])&&t.hasOwnProperty(n)&&r(e,n,o);else r(e,a[1],a[2])}}();function l(e,t){return("string"==typeof e?e:p(e)).indexOf(" "+t+" ")>=0}function d(e,t){var n=p(e),o=n+t;l(n,t)||(e.className=o.substring(1))}function u(e,t){var n,o=p(e);l(e,t)&&(n=o.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function m(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof o?o.call(t,n,t,e):o)||(e.exports=a)},27418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var o={};return"abcdefghijklmnopqrst".split("").forEach((function(e){o[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},o)).join("")}catch(a){return!1}}()?Object.assign:function(e,a){for(var r,s,i=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),c=1;c{var o=n(5826);e.exports=m,e.exports.parse=r,e.exports.compile=function(e,t){return i(r(e,t),t)},e.exports.tokensToFunction=i,e.exports.tokensToRegExp=p;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function r(e,t){for(var n,o=[],r=0,s=0,i="",d=t&&t.delimiter||"/";null!=(n=a.exec(e));){var u=n[0],p=n[1],m=n.index;if(i+=e.slice(s,m),s=m+u.length,p)i+=p[1];else{var f=e[s],g=n[2],h=n[3],b=n[4],v=n[5],x=n[6],y=n[7];i&&(o.push(i),i="");var _=null!=g&&null!=f&&f!==g,w="+"===x||"*"===x,k="?"===x||"*"===x,S=n[2]||d,E=b||v;o.push({name:h||r++,prefix:g||"",delimiter:S,optional:k,repeat:w,partial:_,asterisk:!!y,pattern:E?l(E):y?".*":"[^"+c(S)+"]+?"})}}return s{"use strict";n.d(t,{Z:()=>r});var o=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},o={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=u.reach);k+=w.value.length,w=w.next){var S=w.value;if(t.length>e.length)return;if(!(S instanceof a)){var E,T=1;if(v){if(!(E=r(_,k,e,b))||E.index>=e.length)break;var C=E.index,A=E.index+E[0].length,L=k;for(L+=w.value.length;C>=L;)L+=(w=w.next).value.length;if(k=L-=w.value.length,w.value instanceof a)continue;for(var O=w;O!==t.tail&&(Lu.reach&&(u.reach=R);var M=w.prev;if(P&&(M=c(t,M,P),k+=P.length),l(t,M,T),w=c(t,M,new a(p,h?o.tokenize(N,h):N,x,N)),I&&c(t,w,I),T>1){var D={cause:p+","+f,reach:R};s(e,t,n,w.prev,k,D),u&&D.reach>u.reach&&(u.reach=D.reach)}}}}}}function i(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function c(e,t,n){var o=t.next,a={value:n,prev:t,next:o};return t.next=a,o.prev=a,e.length++,a}function l(e,t,n){for(var o=t.next,a=0;a"+r.content+""},o}(),a=o;o.default=o,a.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},a.languages.markup.tag.inside["attr-value"].inside.entity=a.languages.markup.entity,a.languages.markup.doctype.inside["internal-subset"].inside=a.languages.markup,a.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(a.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:a.languages[t]},n.cdata=/^$/i;var o={"included-cdata":{pattern://i,inside:n}};o["language-"+t]={pattern:/[\s\S]+/,inside:a.languages[t]};var r={};r[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:o},a.languages.insertBefore("markup","cdata",r)}}),Object.defineProperty(a.languages.markup.tag,"addAttribute",{value:function(e,t){a.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:a.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),a.languages.html=a.languages.markup,a.languages.mathml=a.languages.markup,a.languages.svg=a.languages.markup,a.languages.xml=a.languages.extend("markup",{}),a.languages.ssml=a.languages.xml,a.languages.atom=a.languages.xml,a.languages.rss=a.languages.xml,function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},o={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:o},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:o},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:o.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:o.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var a=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],r=o.variable[1].inside,s=0;s]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},a.languages.c=a.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),a.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),a.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},a.languages.c.string],char:a.languages.c.char,comment:a.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:a.languages.c}}}}),a.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete a.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(a),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(a),function(e){var t,n=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+n.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[n,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}});var o={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},a={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:o,number:a,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:o,number:a})}(a),a.languages.javascript=a.languages.extend("clike",{"class-name":[a.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),a.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,a.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:a.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:a.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:a.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:a.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:a.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),a.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:a.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),a.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),a.languages.markup&&(a.languages.markup.tag.addInlined("script","javascript"),a.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),a.languages.js=a.languages.javascript,function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(a),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,o="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),r=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function s(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return o})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return o}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return o})).replace(/<>/g,(function(){return"(?:"+a+"|"+r+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:s(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:s(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:s(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:s(r),lookbehind:!0,greedy:!0},number:{pattern:s(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(a),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(//g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var o=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return o})),r=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+r+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+r+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(o),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+r+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(o),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,o=t.length;n",quot:'"'},c=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(a),a.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:a.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},a.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n0)){var i=p(/^\{$/,/^\}$/);if(-1===i)continue;for(var c=n;c=0&&m(l,"variable-input")}}}}function d(e){return t[n+e]}function u(e,t){t=t||0;for(var n=0;n?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,o=t.inside.interpolation,a=o.inside["interpolation-punctuation"],r=o.pattern.source;function s(t,o){if(e.languages[t])return{pattern:RegExp("((?:"+o+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function i(e,t){return"___"+t.toUpperCase()+"_"+e+"___"}function c(t,n,o){var a={code:t,grammar:n,language:o};return e.hooks.run("before-tokenize",a),a.tokens=e.tokenize(a.code,a.grammar),e.hooks.run("after-tokenize",a),a.tokens}function l(t){var n={};n["interpolation-punctuation"]=a;var r=e.tokenize(t,n);if(3===r.length){var s=[1,1];s.push.apply(s,c(r[1],e.languages.javascript,"javascript")),r.splice.apply(r,s)}return new e.Token("interpolation",r,o.alias,t)}function d(t,n,o){var a=e.tokenize(t,{interpolation:{pattern:RegExp(r),lookbehind:!0}}),s=0,d={},u=c(a.map((function(e){if("string"==typeof e)return e;for(var n,a=e.content;-1!==t.indexOf(n=i(s++,o)););return d[n]=a,n})).join(""),n,o),p=Object.keys(d);return s=0,function e(t){for(var n=0;n=p.length)return;var o=t[n];if("string"==typeof o||"string"==typeof o.content){var a=p[s],r="string"==typeof o?o:o.content,i=r.indexOf(a);if(-1!==i){++s;var c=r.substring(0,i),u=l(d[a]),m=r.substring(i+a.length),f=[];if(c&&f.push(c),f.push(u),m){var g=[m];e(g),f.push.apply(f,g)}"string"==typeof o?(t.splice.apply(t,[n,1].concat(f)),n+=f.length-1):o.content=f}}else{var h=o.content;Array.isArray(h)?e(h):e([h])}}}(u),new e.Token(o,u,"language-"+o,t)}e.languages.javascript["template-string"]=[s("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),s("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),s("svg",/\bsvg/.source),s("markdown",/\b(?:markdown|md)/.source),s("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),s("sql",/\bsql/.source),t].filter(Boolean);var u={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function p(e){return"string"==typeof e?e:Array.isArray(e)?e.map(p).join(""):p(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in u&&function t(n){for(var o=0,a=n.length;o]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(a),function(e){function t(e,t){return RegExp(e.replace(//g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],o=0;o*\.{3}(?:[^{}]|)*\})/.source;function r(e,t){return e=e.replace(//g,(function(){return n})).replace(//g,(function(){return o})).replace(//g,(function(){return a})),RegExp(e,t)}a=r(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=r(/<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:r(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:r(/=/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var s=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(s).join(""):""},i=function(t){for(var n=[],o=0;o0&&n[n.length-1].tagName===s(a.content[0].content[1])&&n.pop():"/>"===a.content[a.content.length-1].content||n.push({tagName:s(a.content[0].content[1]),openedBraces:0}):n.length>0&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:n.length>0&&n[n.length-1].openedBraces>0&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:r=!0),(r||"string"==typeof a)&&n.length>0&&0===n[n.length-1].openedBraces){var c=s(a);o0&&("string"==typeof t[o-1]||"plain-text"===t[o-1].type)&&(c=s(t[o-1])+c,t.splice(o-1,1),o--),t[o]=new e.Token("plain-text",c,null,c)}a.content&&"string"!=typeof a.content&&i(a.content)}};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||i(e.tokens)}))}(a),function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var t={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(t).forEach((function(n){var o=t[n],a=[];/^\w+$/.test(n)||a.push(/\w+/.exec(n)[0]),"diff"===n&&a.push("bold"),e.languages.diff[n]={pattern:RegExp("^(?:["+o+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:a,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(n)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:t})}(a),a.languages.git={comment:/^#.*/m,deleted:/^[-\u2013].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m},a.languages.go=a.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),a.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete a.languages.go["class-name"],function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,o,a,r){if(n.language===o){var s=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof r&&!r(e))return e;for(var a,i=s.length;-1!==n.code.indexOf(a=t(o,i));)++i;return s[i]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,o){if(n.language===o&&n.tokenStack){n.grammar=e.languages[o];var a=0,r=Object.keys(n.tokenStack);!function s(i){for(var c=0;c=r.length);c++){var l=i[c];if("string"==typeof l||l.content&&"string"==typeof l.content){var d=r[a],u=n.tokenStack[d],p="string"==typeof l?l:l.content,m=t(o,d),f=p.indexOf(m);if(f>-1){++a;var g=p.substring(0,f),h=new e.Token(o,e.tokenize(u,n.grammar),"language-"+o,u),b=p.substring(f+m.length),v=[];g&&v.push.apply(v,s([g])),v.push(h),b&&v.push.apply(v,s([b])),"string"==typeof l?i.splice.apply(i,[c,1].concat(v)):l.content=v}}else l.content&&s(l.content)}return i}(n.tokens)}}}})}(a),function(e){e.languages.handlebars={comment:/\{\{![\s\S]*?\}\}/,delimiter:{pattern:/^\{\{\{?|\}\}\}?$/,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][+-]?\d+)?/,boolean:/\b(?:false|true)\b/,block:{pattern:/^(\s*(?:~\s*)?)[#\/]\S+?(?=\s*(?:~\s*)?$|\s)/,lookbehind:!0,alias:"keyword"},brackets:{pattern:/\[[^\]]+\]/,inside:{punctuation:/\[|\]/,variable:/[\s\S]+/}},punctuation:/[!"#%&':()*+,.\/;<=>@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},e.hooks.add("before-tokenize",(function(t){e.languages["markup-templating"].buildPlaceholders(t,"handlebars",/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"handlebars")})),e.languages.hbs=e.languages.handlebars}(a),a.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},a.languages.webmanifest=a.languages.json,a.languages.less=a.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/,operator:/[+\-*\/]/}),a.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}}),a.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/},a.languages.objectivec=a.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete a.languages.objectivec["class-name"],a.languages.objc=a.languages.objectivec,a.languages.ocaml={comment:{pattern:/\(\*[\s\S]*?\*\)/,greedy:!0},char:{pattern:/'(?:[^\\\r\n']|\\(?:.|[ox]?[0-9a-f]{1,3}))'/i,greedy:!0},string:[{pattern:/"(?:\\(?:[\s\S]|\r\n)|[^\\\r\n"])*"/,greedy:!0},{pattern:/\{([a-z_]*)\|[\s\S]*?\|\1\}/,greedy:!0}],number:[/\b(?:0b[01][01_]*|0o[0-7][0-7_]*)\b/i,/\b0x[a-f0-9][a-f0-9_]*(?:\.[a-f0-9_]*)?(?:p[+-]?\d[\d_]*)?(?!\w)/i,/\b\d[\d_]*(?:\.[\d_]*)?(?:e[+-]?\d[\d_]*)?(?!\w)/i],directive:{pattern:/\B#\w+/,alias:"property"},label:{pattern:/\B~\w+/,alias:"property"},"type-variable":{pattern:/\B'\w+/,alias:"function"},variant:{pattern:/`\w+/,alias:"symbol"},keyword:/\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\b/,boolean:/\b(?:false|true)\b/,"operator-like-punctuation":{pattern:/\[[<>|]|[>|]\]|\{<|>\}/,alias:"punctuation"},operator:/\.[.~]|:[=>]|[=<>@^|&+\-*\/$%!?~][!$%&*+\-.\/:<=>?@^|~]*|\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\b/,punctuation:/;;|::|[(){}\[\].,:;#]|\b_\b/},a.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},a.languages.python["string-interpolation"].inside.interpolation.inside.rest=a.languages.python,a.languages.py=a.languages.python,a.languages.reason=a.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),a.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete a.languages.reason.function,function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0,greedy:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,greedy:!0,inside:{atrule:/(?:@[\w-]+|[+=])/}}}),delete e.languages.sass.atrule;var t=/\$[-\w]+|#\{\$[-\w]+\}/,n=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|not|or)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,greedy:!0,inside:{punctuation:/:/,variable:t,operator:n}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,greedy:!0,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:t,operator:n,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/^([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/m,lookbehind:!0,greedy:!0}})}(a),a.languages.scss=a.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),a.languages.insertBefore("scss","atrule",{keyword:[/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),a.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),a.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|hide|show|with)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|not|or)(?=\s)/,lookbehind:!0}}),a.languages.scss.atrule.inside.rest=a.languages.scss,function(e){var t={pattern:/(\b\d+)(?:%|[a-z]+)/,lookbehind:!0},n={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},o={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},url:{pattern:/\burl\((["']?).*?\1\)/i,greedy:!0},string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:else|for|if|return|unless)(?=\s|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,boolean:/\b(?:false|true)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.{2,3}|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],number:n,punctuation:/[{}()\[\];:,]/};o.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^\{|\}$/,alias:"punctuation"},rest:o}},o.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:o}},e.languages.stylus={"atrule-declaration":{pattern:/(^[ \t]*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:o}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:\{[^{}]*\}|\S.*|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:o}},statement:{pattern:/(^[ \t]*)(?:else|for|if|return|unless)[ \t].+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:o}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)(?!\s)[^{\r\n]*(?:;|[^{\r\n,]$(?!(?:\r?\n|\r)(?:\{|\2[ \t])))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:o.interpolation}},rest:o}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t])))/m,lookbehind:!0,inside:{interpolation:o.interpolation,comment:o.comment,punctuation:/[{},]/}},func:o.func,string:o.string,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0,greedy:!0},interpolation:o.interpolation,punctuation:/[{}()\[\];:.]/}}(a),function(e){var t=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var n=e.languages.tsx.tag;n.pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+n.pattern.source+")",n.pattern.flags),n.lookbehind=!0}(a),a.languages.wasm={comment:[/\(;[\s\S]*?;\)/,{pattern:/;;.*/,greedy:!0}],string:{pattern:/"(?:\\[\s\S]|[^"\\])*"/,greedy:!0},keyword:[{pattern:/\b(?:align|offset)=/,inside:{operator:/=/}},{pattern:/\b(?:(?:f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|neg?|nearest|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|sqrt|store(?:8|16|32)?|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))?|memory\.(?:grow|size))\b/,inside:{punctuation:/\./}},/\b(?:anyfunc|block|br(?:_if|_table)?|call(?:_indirect)?|data|drop|elem|else|end|export|func|get_(?:global|local)|global|if|import|local|loop|memory|module|mut|nop|offset|param|result|return|select|set_(?:global|local)|start|table|tee_local|then|type|unreachable)\b/],variable:/\$[\w!#$%&'*+\-./:<=>?@\\^`|~]+/,number:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/,punctuation:/[()]/};const r=a},57874:()=>{!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},o={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:o},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:o},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:o.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:o.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var a=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],r=o.variable[1].inside,s=0;s{!function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var t={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(t).forEach((function(n){var o=t[n],a=[];/^\w+$/.test(n)||a.push(/\w+/.exec(n)[0]),"diff"===n&&a.push("bold"),e.languages.diff[n]={pattern:RegExp("^(?:["+o+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:a,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(n)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:t})}(Prism)},77158:()=>{!function(e){var t=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,n=/(?:[ \t]+(?![ \t])(?:)?|)/.source.replace(//g,(function(){return t})),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,a=/--[\w-]+=(?:|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(//g,(function(){return o})),r={pattern:RegExp(o),greedy:!0},s={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function i(e,t){return e=e.replace(//g,(function(){return a})).replace(//g,(function(){return n})),RegExp(e,t)}e.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:i(/(^(?:ONBUILD)?\w+)(?:)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[r,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:i(/(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:i(/(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \t\\]+)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:i(/(^ONBUILD)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:s,string:r,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,operator:/\\$/m}},comment:s},e.languages.dockerfile=e.languages.docker}(Prism)},74277:()=>{Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json},89425:()=>{Prism.languages.insertBefore("php","variable",{this:{pattern:/\$this\b/,alias:"keyword"},global:/\$(?:GLOBALS|HTTP_RAW_POST_DATA|_(?:COOKIE|ENV|FILES|GET|POST|REQUEST|SERVER|SESSION)|argc|argv|http_response_header|php_errormsg)\b/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/\b(?:parent|self|static)\b/,punctuation:/::|\\/}}})},99945:()=>{!function(e){var t=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],o=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,a=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,r=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:t,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:o,operator:a,punctuation:r};var s={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},i=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:s}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:s}}];e.languages.insertBefore("php","variable",{string:i,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:t,string:i,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:o,operator:a,punctuation:r}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(t){if(/<\?/.test(t.code)){e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)}})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(Prism)},73358:()=>{!function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,o="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),r=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function s(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return o})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return o}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return o})).replace(/<>/g,(function(){return"(?:"+a+"|"+r+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:s(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:s(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:s(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:s(r),lookbehind:!0,greedy:!0},number:{pattern:s(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism)},48682:(e,t,n)=>{var o={"./prism-bash":57874,"./prism-diff":40728,"./prism-docker":77158,"./prism-json":74277,"./prism-php":99945,"./prism-php-extras":89425,"./prism-yaml":73358};function a(e){var t=r(e);return n(t)}function r(e){if(!n.o(o,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return o[e]}a.keys=function(){return Object.keys(o)},a.resolve=r,e.exports=a,a.id=48682},92703:(e,t,n)=>{"use strict";var o=n(50414);function a(){}function r(){}r.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,r,s){if(s!==o){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:r,resetWarningCache:a};return n.PropTypes=n,n}},45697:(e,t,n)=>{e.exports=n(92703)()},50414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},64448:(e,t,n)=>{"use strict";var o=n(67294),a=n(27418),r=n(63840);function s(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n