From 9cd421ca192ecd9b9f60302399b31f5275fd6bc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:35:06 +0000 Subject: [PATCH] deploy: 56c31bc38882204e05c517698369651f25a83f15 --- 404.html | 4 +-- assets/js/0fdfb154.92d21cb6.js | 1 - assets/js/0fdfb154.e06e4790.js | 1 + assets/js/1db39f58.816a0c7a.js | 1 + assets/js/1db39f58.addaff04.js | 1 - assets/js/7c142331.61a1611b.js | 1 - assets/js/7c142331.7076e2de.js | 1 + assets/js/9ba65a7f.1c8ede36.js | 1 - assets/js/9ba65a7f.e6112df6.js | 1 + ...n.1a749a80.js => runtime~main.35c54bbc.js} | 2 +- .../apiato-containers/debugger/index.html | 4 +-- .../documentation/index.html | 4 +-- .../apiato-containers/localization/index.html | 4 +-- .../apiato-containers/overview/index.html | 4 +-- .../apiato-containers/payments/index.html | 4 +-- .../apiato-containers/settings/index.html | 4 +-- .../social-authentication/index.html | 4 +-- .../community-containers/overview/index.html | 4 +-- docs/10.x/contribution-guide/index.html | 4 +-- .../core-features/api-versioning/index.html | 4 +-- .../core-features/authentication/index.html | 4 +-- .../core-features/authorization/index.html | 4 +-- .../core-features/code-generator/index.html | 4 +-- .../core-features/data-caching/index.html | 4 +-- .../default-endpoints/index.html | 4 +-- docs/10.x/core-features/etag/index.html | 4 +-- docs/10.x/core-features/hash-id/index.html | 4 +-- docs/10.x/core-features/pagination/index.html | 4 +-- docs/10.x/core-features/profiler/index.html | 4 +-- .../core-features/query-parameters/index.html | 4 +-- .../core-features/rate-limiting/index.html | 4 +-- .../core-features/useful-commands/index.html | 4 +-- .../user-registration/index.html | 4 +-- docs/10.x/core-features/validation/index.html | 4 +-- docs/10.x/faq/index.html | 4 +-- .../container-installer/index.html | 4 +-- .../conventions-and-principles/index.html | 4 +-- .../getting-started/installation/index.html | 4 +-- .../markdown-features/index.html | 4 +-- docs/10.x/getting-started/requests/index.html | 4 +-- .../10.x/getting-started/responses/index.html | 4 +-- docs/10.x/getting-started/samples/index.html | 4 +-- .../index.html | 4 +-- docs/10.x/index.html | 4 +-- docs/10.x/main-components/actions/index.html | 4 +-- .../main-components/controllers/index.html | 4 +-- .../main-components/exceptions/index.html | 4 +-- docs/10.x/main-components/models/index.html | 4 +-- docs/10.x/main-components/requests/index.html | 4 +-- docs/10.x/main-components/routes/index.html | 4 +-- .../main-components/subactions/index.html | 4 +-- docs/10.x/main-components/tasks/index.html | 4 +-- .../main-components/transformers/index.html | 4 +-- docs/10.x/main-components/views/index.html | 4 +-- .../miscellaneous/tasks-queuing/index.html | 4 +-- .../miscellaneous/tasks-scheduling/index.html | 4 +-- .../miscellaneous/tests-helpers/index.html | 4 +-- .../optional-components/commands/index.html | 4 +-- .../optional-components/configs/index.html | 4 +-- .../optional-components/criterias/index.html | 4 +-- .../optional-components/events/index.html | 4 +-- .../optional-components/factories/index.html | 4 +-- .../optional-components/helpers/index.html | 4 +-- docs/10.x/optional-components/jobs/index.html | 4 +-- .../optional-components/languages/index.html | 4 +-- .../10.x/optional-components/mails/index.html | 4 +-- .../middlewares/index.html | 4 +-- .../optional-components/migrations/index.html | 4 +-- .../notifications/index.html | 4 +-- .../optional-components/providers/index.html | 4 +-- .../repositories/index.html | 4 +-- .../optional-components/seeders/index.html | 4 +-- .../10.x/optional-components/tests/index.html | 4 +-- .../optional-components/values/index.html | 4 +-- docs/10.x/upgrade-guide/index.html | 4 +-- .../documentation/index.html | 4 +-- .../localization/index.html | 4 +-- .../additional-features/overview/index.html | 4 +-- .../social-authentication/index.html | 4 +-- docs/11.x/contribution-guide/index.html | 4 +-- .../core-features/api-versioning/index.html | 4 +-- .../core-features/authentication/index.html | 4 +-- .../core-features/authorization/index.html | 4 +-- .../core-features/code-generator/index.html | 4 +-- .../core-features/data-caching/index.html | 4 +-- .../default-endpoints/index.html | 4 +-- docs/11.x/core-features/etag/index.html | 4 +-- docs/11.x/core-features/hash-id/index.html | 4 +-- docs/11.x/core-features/pagination/index.html | 4 +-- docs/11.x/core-features/profiler/index.html | 4 +-- .../core-features/query-parameters/index.html | 4 +-- .../core-features/rate-limiting/index.html | 4 +-- .../core-features/useful-commands/index.html | 4 +-- .../user-registration/index.html | 4 +-- docs/11.x/core-features/validation/index.html | 4 +-- docs/11.x/faq/index.html | 4 +-- .../container-installer/index.html | 4 +-- .../conventions-and-principles/index.html | 4 +-- .../getting-started/installation/index.html | 4 +-- .../markdown-features/index.html | 4 +-- docs/11.x/getting-started/requests/index.html | 4 +-- .../11.x/getting-started/responses/index.html | 4 +-- docs/11.x/getting-started/samples/index.html | 4 +-- .../index.html | 4 +-- docs/11.x/index.html | 4 +-- docs/11.x/main-components/actions/index.html | 4 +-- .../main-components/controllers/index.html | 4 +-- .../main-components/exceptions/index.html | 4 +-- docs/11.x/main-components/models/index.html | 4 +-- docs/11.x/main-components/requests/index.html | 4 +-- docs/11.x/main-components/routes/index.html | 4 +-- .../main-components/subactions/index.html | 4 +-- docs/11.x/main-components/tasks/index.html | 4 +-- .../main-components/transformers/index.html | 4 +-- docs/11.x/main-components/views/index.html | 4 +-- .../miscellaneous/tasks-queuing/index.html | 4 +-- .../miscellaneous/tasks-scheduling/index.html | 4 +-- .../miscellaneous/tests-helpers/index.html | 4 +-- .../optional-components/commands/index.html | 4 +-- .../optional-components/configs/index.html | 4 +-- .../optional-components/criterias/index.html | 4 +-- .../optional-components/events/index.html | 4 +-- .../optional-components/factories/index.html | 4 +-- .../optional-components/helpers/index.html | 4 +-- docs/11.x/optional-components/jobs/index.html | 4 +-- .../optional-components/languages/index.html | 4 +-- .../11.x/optional-components/mails/index.html | 4 +-- .../middlewares/index.html | 4 +-- .../optional-components/migrations/index.html | 4 +-- .../notifications/index.html | 4 +-- .../optional-components/providers/index.html | 4 +-- .../repositories/index.html | 4 +-- .../optional-components/seeders/index.html | 4 +-- .../11.x/optional-components/tests/index.html | 4 +-- .../optional-components/values/index.html | 4 +-- docs/11.x/upgrade-guide/index.html | 4 +-- docs/9.x/contribution-guide/index.html | 4 +-- .../core-features/admin-dashboard/index.html | 4 +-- .../api-docs-generator/index.html | 4 +-- .../core-features/api-versioning/index.html | 4 +-- .../core-features/authentication/index.html | 4 +-- .../core-features/authorization/index.html | 4 +-- .../core-features/code-generator/index.html | 4 +-- .../9.x/core-features/data-caching/index.html | 4 +-- .../default-endpoints/index.html | 4 +-- docs/9.x/core-features/etag/index.html | 4 +-- docs/9.x/core-features/hash-id/index.html | 4 +-- .../9.x/core-features/localization/index.html | 4 +-- docs/9.x/core-features/pagination/index.html | 4 +-- docs/9.x/core-features/payments/index.html | 4 +-- docs/9.x/core-features/profiler/index.html | 4 +-- .../core-features/query-parameters/index.html | 4 +-- .../core-features/rate-limiting/index.html | 4 +-- .../core-features/requests-monitor/index.html | 4 +-- .../search-query-parameter/index.html | 4 +-- .../social-authentication/index.html | 4 +-- .../core-features/system-settings/index.html | 4 +-- .../core-features/useful-commands/index.html | 4 +-- .../user-registration/index.html | 4 +-- docs/9.x/core-features/validation/index.html | 4 +-- docs/9.x/faq/index.html | 4 +-- .../conventions-and-principles/index.html | 4 +-- .../getting-started/installation/index.html | 4 +-- .../markdown-features/index.html | 4 +-- docs/9.x/getting-started/overview/index.html | 4 +-- docs/9.x/getting-started/requests/index.html | 4 +-- docs/9.x/getting-started/responses/index.html | 4 +-- .../index.html | 4 +-- docs/9.x/index.html | 4 +-- docs/9.x/main-components/actions/index.html | 4 +-- .../main-components/controllers/index.html | 4 +-- docs/9.x/main-components/models/index.html | 4 +-- docs/9.x/main-components/requests/index.html | 4 +-- docs/9.x/main-components/routes/index.html | 4 +-- .../9.x/main-components/subactions/index.html | 4 +-- docs/9.x/main-components/tasks/index.html | 4 +-- .../main-components/transformers/index.html | 4 +-- .../main-components/transporters/index.html | 4 +-- docs/9.x/main-components/views/index.html | 4 +-- .../container-installer/index.html | 4 +-- .../9.x/miscellaneous/magical-call/index.html | 4 +-- docs/9.x/miscellaneous/postman/index.html | 4 +-- .../miscellaneous/tasks-queuing/index.html | 4 +-- .../miscellaneous/tasks-scheduling/index.html | 4 +-- .../miscellaneous/tests-helpers/index.html | 4 +-- .../optional-components/commands/index.html | 4 +-- .../optional-components/configs/index.html | 4 +-- .../optional-components/criterias/index.html | 4 +-- .../9.x/optional-components/events/index.html | 4 +-- .../optional-components/exceptions/index.html | 4 +-- .../optional-components/factories/index.html | 4 +-- docs/9.x/optional-components/jobs/index.html | 4 +-- .../optional-components/languages/index.html | 4 +-- docs/9.x/optional-components/mails/index.html | 4 +-- .../middlewares/index.html | 4 +-- .../optional-components/migrations/index.html | 4 +-- .../notifications/index.html | 4 +-- .../optional-components/providers/index.html | 4 +-- .../repositories/index.html | 4 +-- .../optional-components/seeders/index.html | 4 +-- docs/9.x/optional-components/tests/index.html | 4 +-- .../9.x/optional-components/values/index.html | 4 +-- docs/9.x/upgrade-guide/index.html | 4 +-- .../components/index.html | 4 +-- .../container/index.html | 4 +-- docs/architecture-concepts/index.html | 4 +-- docs/architecture-concepts/porto/index.html | 4 +-- .../request-lifecycle/index.html | 4 +-- docs/components/index.html | 4 +-- .../main-components/actions/index.html | 4 +-- .../main-components/controllers/index.html | 4 +-- .../main-components/exceptions/index.html | 4 +-- docs/components/main-components/index.html | 4 +-- .../main-components/models/index.html | 4 +-- .../main-components/requests/index.html | 4 +-- .../main-components/routes/index.html | 4 +-- .../main-components/subactions/index.html | 4 +-- .../main-components/tasks/index.html | 4 +-- .../main-components/transformers/index.html | 4 +-- .../main-components/views/index.html | 4 +-- .../optional-components/commands/index.html | 4 +-- .../optional-components/configs/index.html | 4 +-- .../optional-components/events/index.html | 4 +-- .../optional-components/factories/index.html | 4 +-- .../optional-components/helpers/index.html | 4 +-- .../components/optional-components/index.html | 4 +-- .../optional-components/jobs/index.html | 4 +-- .../optional-components/mail/index.html | 4 +-- .../middlewares/index.html | 4 +-- .../optional-components/migrations/index.html | 4 +-- .../notifications/index.html | 4 +-- .../optional-components/policies/index.html | 8 ++--- .../repository/criterias/index.html | 4 +-- .../repository/repositories/index.html | 4 +-- .../optional-components/seeders/index.html | 4 +-- .../service-providers/index.html | 4 +-- .../optional-components/tests/index.html | 29 ++++++++++++------- .../optional-components/values/index.html | 4 +-- docs/consulting/index.html | 4 +-- .../api-versioning/index.html | 4 +-- .../code-generator/index.html | 4 +-- docs/framework-features/etag/index.html | 4 +-- docs/framework-features/index.html | 4 +-- docs/framework-features/profiler/index.html | 4 +-- .../rate-limiting/index.html | 4 +-- docs/framework-features/rbac/index.html | 4 +-- .../getting-started/best-practices/index.html | 4 +-- .../customized-laravel-components/index.html | 4 +-- docs/getting-started/installation/index.html | 4 +-- .../components/index.html | 4 +-- .../container/index.html | 4 +-- docs/next/architecture-concepts/index.html | 4 +-- .../architecture-concepts/porto/index.html | 4 +-- .../request-lifecycle/index.html | 4 +-- docs/next/components/index.html | 4 +-- .../main-components/actions/index.html | 4 +-- .../main-components/controllers/index.html | 4 +-- .../main-components/exceptions/index.html | 4 +-- .../components/main-components/index.html | 4 +-- .../main-components/models/index.html | 4 +-- .../main-components/requests/index.html | 4 +-- .../main-components/routes/index.html | 4 +-- .../main-components/subactions/index.html | 4 +-- .../main-components/tasks/index.html | 4 +-- .../main-components/transformers/index.html | 4 +-- .../main-components/views/index.html | 4 +-- .../optional-components/commands/index.html | 4 +-- .../optional-components/configs/index.html | 4 +-- .../optional-components/events/index.html | 4 +-- .../optional-components/factories/index.html | 4 +-- .../optional-components/helpers/index.html | 4 +-- .../components/optional-components/index.html | 4 +-- .../optional-components/jobs/index.html | 4 +-- .../optional-components/mail/index.html | 4 +-- .../middlewares/index.html | 4 +-- .../optional-components/migrations/index.html | 4 +-- .../notifications/index.html | 4 +-- .../optional-components/policies/index.html | 8 ++--- .../repository/criterias/index.html | 4 +-- .../repository/repositories/index.html | 4 +-- .../optional-components/seeders/index.html | 4 +-- .../service-providers/index.html | 4 +-- .../optional-components/tests/index.html | 29 ++++++++++++------- .../optional-components/values/index.html | 4 +-- docs/next/consulting/index.html | 4 +-- .../api-versioning/index.html | 4 +-- .../code-generator/index.html | 4 +-- docs/next/framework-features/etag/index.html | 4 +-- docs/next/framework-features/index.html | 4 +-- .../framework-features/profiler/index.html | 4 +-- .../rate-limiting/index.html | 4 +-- docs/next/framework-features/rbac/index.html | 4 +-- .../getting-started/best-practices/index.html | 4 +-- .../customized-laravel-components/index.html | 4 +-- .../getting-started/installation/index.html | 4 +-- docs/next/pacakges/documentation/index.html | 4 +-- docs/next/pacakges/index.html | 4 +-- docs/next/pacakges/localization/index.html | 4 +-- .../pacakges/social-authentication/index.html | 4 +-- .../prologue/contribution-guide/index.html | 4 +-- docs/next/prologue/release-notes/index.html | 4 +-- docs/next/prologue/upgrade-guide/index.html | 4 +-- docs/next/security/authentication/index.html | 4 +-- docs/next/security/authorization/index.html | 4 +-- .../security/email-varification/index.html | 4 +-- docs/next/security/hash-id/index.html | 4 +-- docs/next/security/password-reset/index.html | 4 +-- docs/next/security/registration/index.html | 4 +-- docs/next/tags/action/index.html | 4 +-- docs/next/tags/api-versioning/index.html | 4 +-- docs/next/tags/architecture/index.html | 4 +-- docs/next/tags/authorization/index.html | 4 +-- docs/next/tags/code-generator/index.html | 4 +-- docs/next/tags/command/index.html | 4 +-- docs/next/tags/component/index.html | 4 +-- docs/next/tags/config/index.html | 4 +-- docs/next/tags/container/index.html | 4 +-- docs/next/tags/controller/index.html | 4 +-- docs/next/tags/criteria/index.html | 4 +-- docs/next/tags/etag/index.html | 4 +-- docs/next/tags/event/index.html | 4 +-- docs/next/tags/exception/index.html | 4 +-- docs/next/tags/factory/index.html | 4 +-- docs/next/tags/framework-feature/index.html | 4 +-- docs/next/tags/helper/index.html | 4 +-- docs/next/tags/index.html | 4 +-- docs/next/tags/job/index.html | 4 +-- docs/next/tags/lifecycle/index.html | 4 +-- docs/next/tags/listener/index.html | 4 +-- docs/next/tags/mail/index.html | 4 +-- docs/next/tags/main-component/index.html | 4 +-- docs/next/tags/middleware/index.html | 4 +-- docs/next/tags/migration/index.html | 4 +-- docs/next/tags/model/index.html | 4 +-- docs/next/tags/notification/index.html | 4 +-- docs/next/tags/optional-component/index.html | 4 +-- docs/next/tags/policy/index.html | 4 +-- docs/next/tags/porto/index.html | 4 +-- docs/next/tags/profiler/index.html | 4 +-- docs/next/tags/queue/index.html | 4 +-- docs/next/tags/rate-limiting/index.html | 4 +-- docs/next/tags/repository/index.html | 4 +-- docs/next/tags/request/index.html | 4 +-- docs/next/tags/response/index.html | 4 +-- .../tags/role-based-access-control/index.html | 4 +-- docs/next/tags/route/index.html | 4 +-- docs/next/tags/seeder/index.html | 4 +-- docs/next/tags/service-provider/index.html | 4 +-- docs/next/tags/sub-action/index.html | 4 +-- docs/next/tags/task/index.html | 4 +-- docs/next/tags/test/index.html | 4 +-- docs/next/tags/testing/index.html | 4 +-- docs/next/tags/transformer/index.html | 4 +-- docs/next/tags/value/index.html | 4 +-- docs/next/tags/view/index.html | 4 +-- docs/pacakges/documentation/index.html | 4 +-- docs/pacakges/index.html | 4 +-- docs/pacakges/localization/index.html | 4 +-- .../pacakges/social-authentication/index.html | 4 +-- docs/prologue/contribution-guide/index.html | 4 +-- docs/prologue/release-notes/index.html | 4 +-- docs/prologue/upgrade-guide/index.html | 4 +-- docs/security/authentication/index.html | 4 +-- docs/security/authorization/index.html | 4 +-- docs/security/email-varification/index.html | 4 +-- docs/security/hash-id/index.html | 4 +-- docs/security/password-reset/index.html | 4 +-- docs/security/registration/index.html | 4 +-- docs/tags/action/index.html | 4 +-- docs/tags/api-versioning/index.html | 4 +-- docs/tags/architecture/index.html | 4 +-- docs/tags/authorization/index.html | 4 +-- docs/tags/code-generator/index.html | 4 +-- docs/tags/command/index.html | 4 +-- docs/tags/component/index.html | 4 +-- docs/tags/config/index.html | 4 +-- docs/tags/container/index.html | 4 +-- docs/tags/controller/index.html | 4 +-- docs/tags/criteria/index.html | 4 +-- docs/tags/etag/index.html | 4 +-- docs/tags/event/index.html | 4 +-- docs/tags/exception/index.html | 4 +-- docs/tags/factory/index.html | 4 +-- docs/tags/framework-feature/index.html | 4 +-- docs/tags/helper/index.html | 4 +-- docs/tags/index.html | 4 +-- docs/tags/job/index.html | 4 +-- docs/tags/lifecycle/index.html | 4 +-- docs/tags/listener/index.html | 4 +-- docs/tags/mail/index.html | 4 +-- docs/tags/main-component/index.html | 4 +-- docs/tags/middleware/index.html | 4 +-- docs/tags/migration/index.html | 4 +-- docs/tags/model/index.html | 4 +-- docs/tags/notification/index.html | 4 +-- docs/tags/optional-component/index.html | 4 +-- docs/tags/policy/index.html | 4 +-- docs/tags/porto/index.html | 4 +-- docs/tags/profiler/index.html | 4 +-- docs/tags/queue/index.html | 4 +-- docs/tags/rate-limiting/index.html | 4 +-- docs/tags/repository/index.html | 4 +-- docs/tags/request/index.html | 4 +-- docs/tags/response/index.html | 4 +-- .../tags/role-based-access-control/index.html | 4 +-- docs/tags/route/index.html | 4 +-- docs/tags/seeder/index.html | 4 +-- docs/tags/service-provider/index.html | 4 +-- docs/tags/sub-action/index.html | 4 +-- docs/tags/task/index.html | 4 +-- docs/tags/test/index.html | 4 +-- docs/tags/testing/index.html | 4 +-- docs/tags/transformer/index.html | 4 +-- docs/tags/value/index.html | 4 +-- docs/tags/view/index.html | 4 +-- index.html | 4 +-- 416 files changed, 855 insertions(+), 841 deletions(-) delete mode 100644 assets/js/0fdfb154.92d21cb6.js create mode 100644 assets/js/0fdfb154.e06e4790.js create mode 100644 assets/js/1db39f58.816a0c7a.js delete mode 100644 assets/js/1db39f58.addaff04.js delete mode 100644 assets/js/7c142331.61a1611b.js create mode 100644 assets/js/7c142331.7076e2de.js delete mode 100644 assets/js/9ba65a7f.1c8ede36.js create mode 100644 assets/js/9ba65a7f.e6112df6.js rename assets/js/{runtime~main.1a749a80.js => runtime~main.35c54bbc.js} (98%) diff --git a/404.html b/404.html index 0c19b65a4..c095d9f14 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.

- + \ No newline at end of file diff --git a/assets/js/0fdfb154.92d21cb6.js b/assets/js/0fdfb154.92d21cb6.js deleted file mode 100644 index c97bc6526..000000000 --- a/assets/js/0fdfb154.92d21cb6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2528],{95788:(e,n,o)=>{o.d(n,{Iu:()=>c,yg:()=>m});var i=o(11504);function t(e,n,o){return n in e?Object.defineProperty(e,n,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[n]=o,e}function r(e,n){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),o.push.apply(o,i)}return o}function a(e){for(var n=1;n=0||(t[o]=e[o]);return t}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(t[o]=e[o])}return t}var s=i.createContext({}),p=function(e){var n=i.useContext(s),o=n;return e&&(o="function"==typeof e?e(n):a(a({},n),e)),o},c=function(e){var n=p(e.components);return i.createElement(s.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},y=i.forwardRef((function(e,n){var o=e.components,t=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(o),y=t,m=d["".concat(s,".").concat(y)]||d[y]||u[y]||r;return o?i.createElement(m,a(a({ref:n},c),{},{components:o})):i.createElement(m,a({ref:n},c))}));function m(e,n){var o=arguments,t=n&&n.mdxType;if("string"==typeof e||t){var r=o.length,a=new Array(r);a[0]=y;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[d]="string"==typeof e?e:t,a[1]=l;for(var p=2;p{o.r(n),o.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var i=o(45072),t=(o(11504),o(95788));const r={title:"Policies",tags:["component","optional-component","policy","authorization","request"]},a=void 0,l={unversionedId:"components/optional-components/policies",id:"version-12.x/components/optional-components/policies",title:"Policies",description:"Apiato policies are just Laravel Policies,",source:"@site/versioned_docs/version-12.x/components/optional-components/policies.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/policies",permalink:"/docs/components/optional-components/policies",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/policies.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"policy",permalink:"/docs/tags/policy"},{label:"authorization",permalink:"/docs/tags/authorization"},{label:"request",permalink:"/docs/tags/request"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Policies",tags:["component","optional-component","policy","authorization","request"]},sidebar:"tutorialSidebar",previous:{title:"Notifications",permalink:"/docs/components/optional-components/notifications"},next:{title:"Repositories",permalink:"/docs/components/optional-components/repository/repositories"}},s={},p=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Registering Policies",id:"registering-policies",level:2},{value:"Policy Auto-Discovery",id:"policy-auto-discovery",level:3},{value:"Policy Registration Flow",id:"policy-registration-flow",level:2},{value:"Helper Methods",id:"helper-methods",level:2}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,i.c)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"Apiato policies are just ",(0,t.yg)("a",{parentName:"p",href:"https://laravel.com/docs/authorization"},"Laravel Policies"),",\nand they function in the exact same way as Laravel policies.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.yg)("p",null,"To generate new policies you may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:policy")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:policy\n")),(0,t.yg)("h2",{id:"rules"},"Rules"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"All Policies:",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"MUST extend the ",(0,t.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Policies\\Policy")," class.",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.yg)("inlineCode",{parentName:"li"},"ParentPolicy"),"."))),(0,t.yg)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy.php"),".")))),(0,t.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.yg)("p",null,"The highlighted section showcases the policy registration point:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 UserPolicy.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 Providers\n // highlight-start\n \u251c\u2500\u2500 AuthServiceProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.yg)("h2",{id:"code-example"},"Code Example"),(0,t.yg)("p",null,"Policies are defined exactly as you would define them in Laravel."),(0,t.yg)("h2",{id:"registering-policies"},"Registering Policies"),(0,t.yg)("p",null,"Once the policy class has been created, it needs to be registered.\nRegistering policies is\nhow we can inform Apiato which policy to use when authorizing actions against a given model type."),(0,t.yg)("p",null,"Registering policies can be done\nby adding them to the ",(0,t.yg)("inlineCode",{parentName:"p"},"policies")," array in the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\AuthServiceProvider")," class."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\AuthServiceProvider as ParentAuthProvider;\n\nclass AuthServiceProvider extends ParentAuthProvider\n{\n protected $policies = [\n Post::class => PostPolicy::class,\n ];\n}\n")),(0,t.yg)("p",null,"To generate an event service provider\nyou may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.yg)("p",null,"Remember to also register the ",(0,t.yg)("inlineCode",{parentName:"p"},"AuthServiceProvider")," in the container's ",(0,t.yg)("inlineCode",{parentName:"p"},"MainServiceProvider"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n protected array $serviceProviders = [\n // ... Other service providers\n AuthServiceProvider::class,\n ];\n}\n")),(0,t.yg)("h3",{id:"policy-auto-discovery"},"Policy Auto-Discovery"),(0,t.yg)("p",null,"Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies.\nThis automatic discovery process relies on adhering to standard Apiato naming conventions for policies."),(0,t.yg)("p",null,"By following the ",(0,t.yg)("a",{parentName:"p",href:"#rules"},"rules")," outlined above, you allow Apiato to automatically discover your policies."),(0,t.yg)("p",null,"To summarize:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Policies must be stored within the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"The policy name should mirror the corresponding model's name while appending a ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, a ",(0,t.yg)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy")," policy class.")),(0,t.yg)("h2",{id:"policy-registration-flow"},"Policy Registration Flow"),(0,t.yg)("p",null,"In case you are going to register your policies manually, and don't want to use the auto-discovery feature,\nyou may want to understand the policy registration process.\nHere is a breakdown of the registration flow."),(0,t.yg)("p",null,"Consider the following folder structure:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 DemoPolicy.php \u2500\u25ba\u2500\u2510\n \u2502 \u2514\u2500\u2500 ... \u2502 \n \u2514\u2500\u2500 Providers \u25bc\n \u251c\u2500\u2500 AuthServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500registered\u2500in\u2500\u25c4\u2518\n \u2514\u2500\u2500 ...\n\n")),(0,t.yg)("p",null,"The following diagram illustrates the registration flow of policies in the above folder structure:"),(0,t.yg)("mermaid",{value:"graph LR\n subgraph Container\n MainServiceProvider\n AuthServiceProvider\n DemoPolicy\n end\n \n MainServiceProvider --\x3e|loads| AuthServiceProvider\n AuthServiceProvider --\x3e|registered in| MainServiceProvider\n DemoPolicy --\x3e|registered in| AuthServiceProvider\n\n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eMainServiceProvider\n end"}),(0,t.yg)("h2",{id:"helper-methods"},"Helper Methods"),(0,t.yg)("blockquote",null,(0,t.yg)("p",{parentName:"blockquote"},"Available in v12.2.0 and above.")),(0,t.yg)("p",null,"All models are equipped with the ",(0,t.yg)("inlineCode",{parentName:"p"},"owns")," and ",(0,t.yg)("inlineCode",{parentName:"p"},"isOwnedBy")," methods,\nmade available through the ",(0,t.yg)("inlineCode",{parentName:"p"},"Apiato\\Core\\Traits\\CanOwnTrait")," trait.\nThese methods offer a convenient way to determine if a model is owned by another model or if a model owns another model."),(0,t.yg)("p",null,"These methods support all types of relationships, as demonstrated below:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// Check if a user owns a post\n$user->owns($post);\n\n// Check if a post is owned by a user\n$post->isOwnedBy($user);\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0fdfb154.e06e4790.js b/assets/js/0fdfb154.e06e4790.js new file mode 100644 index 000000000..0a54a1082 --- /dev/null +++ b/assets/js/0fdfb154.e06e4790.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2528],{95788:(e,n,o)=>{o.d(n,{Iu:()=>c,yg:()=>m});var i=o(11504);function t(e,n,o){return n in e?Object.defineProperty(e,n,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[n]=o,e}function r(e,n){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),o.push.apply(o,i)}return o}function a(e){for(var n=1;n=0||(t[o]=e[o]);return t}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(t[o]=e[o])}return t}var s=i.createContext({}),p=function(e){var n=i.useContext(s),o=n;return e&&(o="function"==typeof e?e(n):a(a({},n),e)),o},c=function(e){var n=p(e.components);return i.createElement(s.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},y=i.forwardRef((function(e,n){var o=e.components,t=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(o),y=t,m=d["".concat(s,".").concat(y)]||d[y]||u[y]||r;return o?i.createElement(m,a(a({ref:n},c),{},{components:o})):i.createElement(m,a({ref:n},c))}));function m(e,n){var o=arguments,t=n&&n.mdxType;if("string"==typeof e||t){var r=o.length,a=new Array(r);a[0]=y;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[d]="string"==typeof e?e:t,a[1]=l;for(var p=2;p{o.r(n),o.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var i=o(45072),t=(o(11504),o(95788));const r={title:"Policies",tags:["component","optional-component","policy","authorization","request"]},a=void 0,l={unversionedId:"components/optional-components/policies",id:"version-12.x/components/optional-components/policies",title:"Policies",description:"Apiato policies are just Laravel Policies,",source:"@site/versioned_docs/version-12.x/components/optional-components/policies.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/policies",permalink:"/docs/components/optional-components/policies",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/versioned_docs/version-12.x/components/optional-components/policies.md",tags:[{label:"component",permalink:"/docs/tags/component"},{label:"optional-component",permalink:"/docs/tags/optional-component"},{label:"policy",permalink:"/docs/tags/policy"},{label:"authorization",permalink:"/docs/tags/authorization"},{label:"request",permalink:"/docs/tags/request"}],version:"12.x",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1707229167,formattedLastUpdatedAt:"Feb 6, 2024",frontMatter:{title:"Policies",tags:["component","optional-component","policy","authorization","request"]},sidebar:"tutorialSidebar",previous:{title:"Notifications",permalink:"/docs/components/optional-components/notifications"},next:{title:"Repositories",permalink:"/docs/components/optional-components/repository/repositories"}},s={},p=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Registering Policies",id:"registering-policies",level:2},{value:"Policy Auto-Discovery",id:"policy-auto-discovery",level:3},{value:"Policy Registration Flow",id:"policy-registration-flow",level:2},{value:"Helper Methods",id:"helper-methods",level:2}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,i.c)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"Apiato policies are just ",(0,t.yg)("a",{parentName:"p",href:"https://laravel.com/docs/authorization"},"Laravel Policies"),",\nand they function in the exact same way as Laravel policies.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.yg)("p",null,"To generate new policies you may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:policy")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:policy\n")),(0,t.yg)("h2",{id:"rules"},"Rules"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"All Policies:",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"MUST extend the ",(0,t.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Policies\\Policy")," class.",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.yg)("inlineCode",{parentName:"li"},"ParentPolicy"),"."))),(0,t.yg)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy.php"),".")))),(0,t.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.yg)("p",null,"The highlighted section showcases the policy registration point:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 UserPolicy.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 Providers\n // highlight-start\n \u251c\u2500\u2500 AuthServiceProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.yg)("h2",{id:"code-example"},"Code Example"),(0,t.yg)("p",null,"Policies are defined exactly as you would define them in Laravel."),(0,t.yg)("h2",{id:"registering-policies"},"Registering Policies"),(0,t.yg)("p",null,"Once the policy class has been created, it needs to be registered.\nRegistering policies is\nhow we can inform Apiato which policy to use when authorizing actions against a given model type."),(0,t.yg)("p",null,"Registering policies can be done\nby adding them to the ",(0,t.yg)("inlineCode",{parentName:"p"},"policies")," array in the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\AuthServiceProvider")," class."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\AuthServiceProvider as ParentAuthProvider;\n\nclass AuthServiceProvider extends ParentAuthProvider\n{\n protected $policies = [\n Post::class => PostPolicy::class,\n ];\n}\n")),(0,t.yg)("p",null,"To generate an event service provider\nyou may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.yg)("p",null,"Remember to also register the ",(0,t.yg)("inlineCode",{parentName:"p"},"AuthServiceProvider")," in the container's ",(0,t.yg)("inlineCode",{parentName:"p"},"MainServiceProvider"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n protected array $serviceProviders = [\n // ... Other service providers\n AuthServiceProvider::class,\n ];\n}\n")),(0,t.yg)("h3",{id:"policy-auto-discovery"},"Policy Auto-Discovery"),(0,t.yg)("p",null,"Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies.\nThis automatic discovery process relies on adhering to standard Apiato naming conventions for policies."),(0,t.yg)("p",null,"By following the ",(0,t.yg)("a",{parentName:"p",href:"#rules"},"rules")," outlined above, you allow Apiato to automatically discover your policies."),(0,t.yg)("p",null,"To summarize:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Policies must be stored within the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"The policy name should mirror the corresponding model's name while appending a ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, a ",(0,t.yg)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy")," policy class.")),(0,t.yg)("h2",{id:"policy-registration-flow"},"Policy Registration Flow"),(0,t.yg)("p",null,"In case you are going to register your policies manually, and don't want to use the auto-discovery feature,\nyou may want to understand the policy registration process.\nHere is a breakdown of the registration flow."),(0,t.yg)("p",null,"Consider the following folder structure:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 DemoPolicy.php \u2500\u25ba\u2500\u2510\n \u2502 \u2514\u2500\u2500 ... \u2502 \n \u2514\u2500\u2500 Providers \u25bc\n \u251c\u2500\u2500 AuthServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500registered\u2500in\u2500\u25c4\u2518\n \u2514\u2500\u2500 ...\n\n")),(0,t.yg)("p",null,"The following diagram illustrates the registration flow of policies in the above folder structure:"),(0,t.yg)("mermaid",{value:"graph LR\n subgraph Container\n MainServiceProvider\n AuthServiceProvider\n DemoPolicy\n end\n \n MainServiceProvider --\x3e|loads| AuthServiceProvider\n AuthServiceProvider --\x3e|registered in| MainServiceProvider\n DemoPolicy --\x3e|registered in| AuthServiceProvider\n\n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eMainServiceProvider\n end"}),(0,t.yg)("h2",{id:"helper-methods"},"Helper Methods"),(0,t.yg)("blockquote",null,(0,t.yg)("p",{parentName:"blockquote"},"Available since Core v8.7.0")),(0,t.yg)("p",null,"All models are equipped with the ",(0,t.yg)("inlineCode",{parentName:"p"},"owns")," and ",(0,t.yg)("inlineCode",{parentName:"p"},"isOwnedBy")," methods,\nmade available through the ",(0,t.yg)("inlineCode",{parentName:"p"},"Apiato\\Core\\Traits\\CanOwnTrait")," trait.\nThese methods offer a convenient way to determine if a model is owned by another model or if a model owns another model."),(0,t.yg)("p",null,"These methods support all types of relationships, as demonstrated below:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// Check if a user owns a post\n$user->owns($post);\n\n// Check if a post is owned by a user\n$post->isOwnedBy($user);\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1db39f58.816a0c7a.js b/assets/js/1db39f58.816a0c7a.js new file mode 100644 index 000000000..e9112493a --- /dev/null +++ b/assets/js/1db39f58.816a0c7a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2208],{95788:(e,t,n)=>{n.d(t,{Iu:()=>d,yg:()=>g});var a=n(11504);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 r(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):r(r({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",h={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=o(e,["components","mdxType","originalType","parentName"]),u=p(n),c=i,g=u["".concat(l,".").concat(c)]||u[c]||h[c]||s;return n?a.createElement(g,r(r({ref:t},d),{},{components:n})):a.createElement(g,r({ref:t},d))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,r=new Array(s);r[0]=c;var o={};for(var l in t)hasOwnProperty.call(t,l)&&(o[l]=t[l]);o.originalType=e,o[u]="string"==typeof e?e:i,r[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>o,toc:()=>p});var a=n(45072),i=(n(11504),n(95788));const s={title:"Tests",tags:["component","optional-component","test"]},r=void 0,o={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:1707229167,formattedLastUpdatedAt:"Feb 6, 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 Tests",id:"functional-tests-1",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:"method",level:4},{value:"auth",id:"method",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:"assertCriteriaPushedToRepository",id:"assertcriteriapushedtorepository",level:4},{value:"assertNoCriteriaPushedToRepository",id:"assertnocriteriapushedtorepository",level:4},{value:"allowAddRequestCriteriaInvocation",id:"allowaddrequestcriteriainvocation",level:4},{value:"Faker",id:"faker",level:2},{value:"Test Helper Methods",id:"test-helper-methods",level:2},{value:"createSpyWithRepository",id:"createspywithrepository",level:4},{value:"inIds",id:"inids",level:4},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},u="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.c)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("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.yg)("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.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.yg)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.yg)("h2",{id:"definitions"},"Definitions"),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"unit-tests"},"Unit tests"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"functional-tests"},"Functional tests"),(0,i.yg)("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.yg)("h2",{id:"rules"},"Rules"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All container-specific tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests")," directory."),(0,i.yg)("li",{parentName:"ul"},"Functional tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional")," directory."),(0,i.yg)("li",{parentName:"ul"},"API tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/API")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\ApiTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))),(0,i.yg)("li",{parentName:"ul"},"CLI tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/CLI")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\CliTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))))),(0,i.yg)("li",{parentName:"ul"},"Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"Directory structure MUST exactly match the Container's directory structure."))))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\ShipTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"ContainerTestCases")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"ShipTestCase")," MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.yg)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.yg)("pre",null,(0,i.yg)("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 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 CLI\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateAdminCommandTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 ApiTestCase.php\n\u2502 \u2502 \u2514\u2500\u2500 CliTestCase.php\n\u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u251c\u2500\u2500 Actions\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 AnotherDirectory\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 ...\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UI\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u251c\u2500\u2500 ContainerTestCase.php\n\u2502 \u251c\u2500\u2500 FunctionalTestCase.php\n\u2502 \u2514\u2500\u2500 UnitTestCase.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 ShipTestCase.php\n")),(0,i.yg)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\Tests\\Functional\\API;\n\nuse App\\Containers\\AppSection\\User\\Data\\Factories\\UserFactory;\nuse App\\Containers\\AppSection\\User\\Tests\\Functional\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\nuse PHPUnit\\Framework\\Attributes\\CoversNothing;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('user')]\n#[CoversNothing]\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.yg)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.yg)("h2",{id:"functional-tests-1"},"Functional Tests"),(0,i.yg)("h3",{id:"properties"},"Properties"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"endpoint"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.yg)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"auth"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.yg)("p",null,"In the context of testing, when ",(0,i.yg)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"access"},"access"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("p",null,"By setting the desired roles and permissions in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("h3",{id:"methods"},"Methods"),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguser"},"getTestingUser"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#endpoint-method"},"endpoint"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#auth-method"},"auth")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"makecall"},"makeCall"),(0,i.yg)("p",null,"To make a request to your application, you may invoke the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.yg)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"injectid"},"injectId"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this example, the original endpoint is ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.yg)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.yg)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.yg)("p",null,"By default, ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"When the ",(0,i.yg)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.yg)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"By utilizing the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.yg)("p",null,"When you call ",(0,i.yg)("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.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.yg)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.yg)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"The order in which you call ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.yg)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.yg)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertcriteriapushedtorepository"},"assertCriteriaPushedToRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertnocriteriapushedtorepository"},"assertNoCriteriaPushedToRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#allowaddrequestcriteriainvocation"},"allowAddRequestCriteriaInvocation")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.yg)("p",null,"Here's an example of how to use ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.yg)("p",null,"In the code snippet above, ",(0,i.yg)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.yg)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.yg)("p",null,"In this case, the assertion will ignore the ",(0,i.yg)("inlineCode",{parentName:"p"},"id"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.yg)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.yg)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.yg)("p",null,"By using the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.5.0")),(0,i.yg)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.yg)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"getgatemock"},"getGateMock"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.5.0")),(0,i.yg)("p",null,"This assertion helps you to test whether the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.yg)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this code, we're examining the testing of the ",(0,i.yg)("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.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertcriteriapushedtorepository"},"assertCriteriaPushedToRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Asserts that a criteria is pushed to a repository."),(0,i.yg)("p",null,"In the following example, we want to test\nif the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserIsAdminCriteria")," is pushed to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserRepository")," when the ",(0,i.yg)("inlineCode",{parentName:"p"},"ListUsersTask")," is called with the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAdminUsers(): void\n{\n $this->assertCriteriaPushedToRepository(\n UserRepository::class,\n UserIsAdminCriteria::class,\n ['admin' => true],\n );\n $task = app(ListUsersTask::class);\n\n $task->admin();\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertnocriteriapushedtorepository"},"assertNoCriteriaPushedToRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Asserts that no criteria is pushed to a repository."),(0,i.yg)("p",null,"In the following example, we want to test\nif no criteria is pushed to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserRepository")," when the ",(0,i.yg)("inlineCode",{parentName:"p"},"ListUsersTask"),"'s ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," method is called with a ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," value."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAllUsers(): void\n{\n $this->assertNoCriteriaPushedToRepository(UserRepository::class);\n $task = app(ListUsersTask::class);\n\n $task->admin(null);\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"allowaddrequestcriteriainvocation"},"allowAddRequestCriteriaInvocation"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Allow ",(0,i.yg)("inlineCode",{parentName:"p"},"addRequestCriteria")," method invocation on the repository mock.\nThis is particularly useful when you want to test a repository that uses the\n",(0,i.yg)("a",{parentName:"p",href:"/docs/components/optional-components/repository/repositories#requestcriteria"},"RequestCriteria"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAdminUsers(): void\n{\n $repositoryMock = $this->assertCriteriaPushedToRepository(\n UserRepository::class,\n UserIsAdminCriteria::class,\n ['admin' => true],\n );\n // highlight-next-line\n $this->allowAddRequestCriteriaInvocation($repositoryMock);\n $task = app(ListUsersTask::class);\n\n $task->admin();\n}\n")),(0,i.yg)("h2",{id:"faker"},"Faker"),(0,i.yg)("p",null,"An instance of ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.yg)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.yg)("h2",{id:"test-helper-methods"},"Test Helper Methods"),(0,i.yg)("p",null,"Apiato provides a variety of helper methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#createspywithrepository"},"createSpyWithRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"createspywithrepository"},"createSpyWithRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"This method is useful when you want to test if a repository method is called within an Action, SubAction or a Task."),(0,i.yg)("p",null,"In the following example,\nwe want to test if the ",(0,i.yg)("inlineCode",{parentName:"p"},"run")," method of the ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserTask")," is called within the ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserAction"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanCreateUser(): void\n{\n $data = [\n 'email' => 'gandalf@the.grey',\n 'password' => 'you-shall-not-pass',\n ];\n $taskSpy = $this->createSpyWithRepository(CreateUserTask::class, UserRepository::class);\n $action = app(CreateUserAction::class);\n\n $action->run($data);\n \n $taskSpy->shouldHaveReceived('run')->once()->with($data);\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"inids"},"inIds"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.yg)("p",null,"By leveraging the ",(0,i.yg)("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.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in test classes.")),(0,i.yg)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.yg)("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.yg)("p",null,"To create your live testing data, navigate to the ",(0,i.yg)("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.yg)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.yg)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1db39f58.addaff04.js b/assets/js/1db39f58.addaff04.js deleted file mode 100644 index b39b6848c..000000000 --- a/assets/js/1db39f58.addaff04.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[2208],{95788:(e,t,n)=>{n.d(t,{Iu:()=>d,yg:()=>g});var a=n(11504);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)},u="mdxType",h={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"]),u=p(n),c=i,g=u["".concat(l,".").concat(c)]||u[c]||h[c]||s;return n?a.createElement(g,o(o({ref:t},d),{},{components:n})):a.createElement(g,o({ref:t},d))}));function g(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[u]="string"==typeof e?e:i,o[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>r,toc:()=>p});var a=n(45072),i=(n(11504),n(95788));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:1707153375,formattedLastUpdatedAt:"Feb 5, 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:"method",level:4},{value:"auth",id:"method",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},u="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.c)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("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.yg)("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.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.yg)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.yg)("h2",{id:"definitions"},"Definitions"),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"unit-tests"},"Unit tests"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"functional-tests"},"Functional tests"),(0,i.yg)("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.yg)("h2",{id:"rules"},"Rules"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All container-specific tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests")," directory."),(0,i.yg)("li",{parentName:"ul"},"Functional tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional")," directory."),(0,i.yg)("li",{parentName:"ul"},"API tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/API")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\ApiTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))),(0,i.yg)("li",{parentName:"ul"},"CLI tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/CLI")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\CliTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))))),(0,i.yg)("li",{parentName:"ul"},"Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"Directory structure MUST exactly match the Container's directory structure."))))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\ShipTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"ContainerTestCases")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"ShipTestCase")," MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.yg)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.yg)("pre",null,(0,i.yg)("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 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 CLI\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateAdminCommandTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 ApiTestCase.php\n\u2502 \u2502 \u2514\u2500\u2500 CliTestCase.php\n\u2502 \u251c\u2500\u2500 Unit\n\u2502 \u2502 \u251c\u2500\u2500 Actions\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 AnotherDirectory\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 ...\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UI\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u251c\u2500\u2500 ContainerTestCase.php\n\u2502 \u251c\u2500\u2500 FunctionalTestCase.php\n\u2502 \u2514\u2500\u2500 UnitTestCase.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 ShipTestCase.php\n")),(0,i.yg)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\Tests\\Functional\\API;\n\nuse App\\Containers\\AppSection\\User\\Data\\Factories\\UserFactory;\nuse App\\Containers\\AppSection\\User\\Tests\\Functional\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\nuse PHPUnit\\Framework\\Attributes\\CoversNothing;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('user')]\n#[CoversNothing]\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.yg)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.yg)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.yg)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.yg)("h3",{id:"properties"},"Properties"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"endpoint"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.yg)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"auth"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.yg)("p",null,"In the context of testing, when ",(0,i.yg)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"access"},"access"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("p",null,"By setting the desired roles and permissions in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("h3",{id:"methods"},"Methods"),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguser"},"getTestingUser"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#endpoint-method"},"endpoint"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#auth-method"},"auth")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"makecall"},"makeCall"),(0,i.yg)("p",null,"To make a request to your application, you may invoke the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.yg)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"injectid"},"injectId"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this example, the original endpoint is ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.yg)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.yg)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.yg)("p",null,"By default, ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"When the ",(0,i.yg)("a",{parentName:"p",href:"/docs/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.yg)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"By utilizing the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.yg)("p",null,"When you call ",(0,i.yg)("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.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.yg)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.yg)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"The order in which you call ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.yg)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.yg)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.yg)("p",null,"Here's an example of how to use ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.yg)("p",null,"In the code snippet above, ",(0,i.yg)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.yg)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.yg)("p",null,"In this case, the assertion will ignore the ",(0,i.yg)("inlineCode",{parentName:"p"},"id"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.yg)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.yg)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.yg)("p",null,"By using the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.yg)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.yg)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"getgatemock"},"getGateMock"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.yg)("p",null,"This assertion helps you to test whether the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.yg)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this code, we're examining the testing of the ",(0,i.yg)("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.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"inids"},"inIds"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.yg)("p",null,"By leveraging the ",(0,i.yg)("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.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("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.yg)("h2",{id:"faker"},"Faker"),(0,i.yg)("p",null,"An instance of ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.yg)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.yg)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.yg)("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.yg)("p",null,"To create your live testing data, navigate to the ",(0,i.yg)("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.yg)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.yg)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7c142331.61a1611b.js b/assets/js/7c142331.61a1611b.js deleted file mode 100644 index 785241df0..000000000 --- a/assets/js/7c142331.61a1611b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[4164],{95788:(e,n,o)=>{o.d(n,{Iu:()=>c,yg:()=>m});var i=o(11504);function t(e,n,o){return n in e?Object.defineProperty(e,n,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[n]=o,e}function r(e,n){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),o.push.apply(o,i)}return o}function a(e){for(var n=1;n=0||(t[o]=e[o]);return t}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(t[o]=e[o])}return t}var s=i.createContext({}),p=function(e){var n=i.useContext(s),o=n;return e&&(o="function"==typeof e?e(n):a(a({},n),e)),o},c=function(e){var n=p(e.components);return i.createElement(s.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},y=i.forwardRef((function(e,n){var o=e.components,t=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(o),y=t,m=d["".concat(s,".").concat(y)]||d[y]||u[y]||r;return o?i.createElement(m,a(a({ref:n},c),{},{components:o})):i.createElement(m,a({ref:n},c))}));function m(e,n){var o=arguments,t=n&&n.mdxType;if("string"==typeof e||t){var r=o.length,a=new Array(r);a[0]=y;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[d]="string"==typeof e?e:t,a[1]=l;for(var p=2;p{o.r(n),o.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var i=o(45072),t=(o(11504),o(95788));const r={title:"Policies",tags:["component","optional-component","policy","authorization","request"]},a=void 0,l={unversionedId:"components/optional-components/policies",id:"components/optional-components/policies",title:"Policies",description:"Apiato policies are just Laravel Policies,",source:"@site/docs/components/optional-components/policies.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/policies",permalink:"/docs/next/components/optional-components/policies",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/policies.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"policy",permalink:"/docs/next/tags/policy"},{label:"authorization",permalink:"/docs/next/tags/authorization"},{label:"request",permalink:"/docs/next/tags/request"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1697511591,formattedLastUpdatedAt:"Oct 17, 2023",frontMatter:{title:"Policies",tags:["component","optional-component","policy","authorization","request"]},sidebar:"tutorialSidebar",previous:{title:"Notifications",permalink:"/docs/next/components/optional-components/notifications"},next:{title:"Repositories",permalink:"/docs/next/components/optional-components/repository/repositories"}},s={},p=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Registering Policies",id:"registering-policies",level:2},{value:"Policy Auto-Discovery",id:"policy-auto-discovery",level:3},{value:"Policy Registration Flow",id:"policy-registration-flow",level:2},{value:"Helper Methods",id:"helper-methods",level:2}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,i.c)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"Apiato policies are just ",(0,t.yg)("a",{parentName:"p",href:"https://laravel.com/docs/authorization"},"Laravel Policies"),",\nand they function in the exact same way as Laravel policies.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.yg)("p",null,"To generate new policies you may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:policy")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:policy\n")),(0,t.yg)("h2",{id:"rules"},"Rules"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"All Policies:",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"MUST extend the ",(0,t.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Policies\\Policy")," class.",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.yg)("inlineCode",{parentName:"li"},"ParentPolicy"),"."))),(0,t.yg)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy.php"),".")))),(0,t.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.yg)("p",null,"The highlighted section showcases the policy registration point:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 UserPolicy.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 Providers\n // highlight-start\n \u251c\u2500\u2500 AuthServiceProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.yg)("h2",{id:"code-example"},"Code Example"),(0,t.yg)("p",null,"Policies are defined exactly as you would define them in Laravel."),(0,t.yg)("h2",{id:"registering-policies"},"Registering Policies"),(0,t.yg)("p",null,"Once the policy class has been created, it needs to be registered.\nRegistering policies is\nhow we can inform Apiato which policy to use when authorizing actions against a given model type."),(0,t.yg)("p",null,"Registering policies can be done\nby adding them to the ",(0,t.yg)("inlineCode",{parentName:"p"},"policies")," array in the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\AuthServiceProvider")," class."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\AuthServiceProvider as ParentAuthProvider;\n\nclass AuthServiceProvider extends ParentAuthProvider\n{\n protected $policies = [\n Post::class => PostPolicy::class,\n ];\n}\n")),(0,t.yg)("p",null,"To generate an event service provider\nyou may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.yg)("p",null,"Remember to also register the ",(0,t.yg)("inlineCode",{parentName:"p"},"AuthServiceProvider")," in the container's ",(0,t.yg)("inlineCode",{parentName:"p"},"MainServiceProvider"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n protected array $serviceProviders = [\n // ... Other service providers\n AuthServiceProvider::class,\n ];\n}\n")),(0,t.yg)("h3",{id:"policy-auto-discovery"},"Policy Auto-Discovery"),(0,t.yg)("p",null,"Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies.\nThis automatic discovery process relies on adhering to standard Apiato naming conventions for policies."),(0,t.yg)("p",null,"By following the ",(0,t.yg)("a",{parentName:"p",href:"#rules"},"rules")," outlined above, you allow Apiato to automatically discover your policies."),(0,t.yg)("p",null,"To summarize:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Policies must be stored within the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"The policy name should mirror the corresponding model's name while appending a ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, a ",(0,t.yg)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy")," policy class.")),(0,t.yg)("h2",{id:"policy-registration-flow"},"Policy Registration Flow"),(0,t.yg)("p",null,"In case you are going to register your policies manually, and don't want to use the auto-discovery feature,\nyou may want to understand the policy registration process.\nHere is a breakdown of the registration flow."),(0,t.yg)("p",null,"Consider the following folder structure:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 DemoPolicy.php \u2500\u25ba\u2500\u2510\n \u2502 \u2514\u2500\u2500 ... \u2502 \n \u2514\u2500\u2500 Providers \u25bc\n \u251c\u2500\u2500 AuthServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500registered\u2500in\u2500\u25c4\u2518\n \u2514\u2500\u2500 ...\n\n")),(0,t.yg)("p",null,"The following diagram illustrates the registration flow of policies in the above folder structure:"),(0,t.yg)("mermaid",{value:"graph LR\n subgraph Container\n MainServiceProvider\n AuthServiceProvider\n DemoPolicy\n end\n \n MainServiceProvider --\x3e|loads| AuthServiceProvider\n AuthServiceProvider --\x3e|registered in| MainServiceProvider\n DemoPolicy --\x3e|registered in| AuthServiceProvider\n\n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eMainServiceProvider\n end"}),(0,t.yg)("h2",{id:"helper-methods"},"Helper Methods"),(0,t.yg)("blockquote",null,(0,t.yg)("p",{parentName:"blockquote"},"Available in v12.2.0 and above.")),(0,t.yg)("p",null,"All models are equipped with the ",(0,t.yg)("inlineCode",{parentName:"p"},"owns")," and ",(0,t.yg)("inlineCode",{parentName:"p"},"isOwnedBy")," methods,\nmade available through the ",(0,t.yg)("inlineCode",{parentName:"p"},"Apiato\\Core\\Traits\\CanOwnTrait")," trait.\nThese methods offer a convenient way to determine if a model is owned by another model or if a model owns another model."),(0,t.yg)("p",null,"These methods support all types of relationships, as demonstrated below:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// Check if a user owns a post\n$user->owns($post);\n\n// Check if a post is owned by a user\n$post->isOwnedBy($user);\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7c142331.7076e2de.js b/assets/js/7c142331.7076e2de.js new file mode 100644 index 000000000..ab4dfeaf0 --- /dev/null +++ b/assets/js/7c142331.7076e2de.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[4164],{95788:(e,n,o)=>{o.d(n,{Iu:()=>c,yg:()=>m});var i=o(11504);function t(e,n,o){return n in e?Object.defineProperty(e,n,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[n]=o,e}function r(e,n){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),o.push.apply(o,i)}return o}function a(e){for(var n=1;n=0||(t[o]=e[o]);return t}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(t[o]=e[o])}return t}var s=i.createContext({}),p=function(e){var n=i.useContext(s),o=n;return e&&(o="function"==typeof e?e(n):a(a({},n),e)),o},c=function(e){var n=p(e.components);return i.createElement(s.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},y=i.forwardRef((function(e,n){var o=e.components,t=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(o),y=t,m=d["".concat(s,".").concat(y)]||d[y]||u[y]||r;return o?i.createElement(m,a(a({ref:n},c),{},{components:o})):i.createElement(m,a({ref:n},c))}));function m(e,n){var o=arguments,t=n&&n.mdxType;if("string"==typeof e||t){var r=o.length,a=new Array(r);a[0]=y;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[d]="string"==typeof e?e:t,a[1]=l;for(var p=2;p{o.r(n),o.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var i=o(45072),t=(o(11504),o(95788));const r={title:"Policies",tags:["component","optional-component","policy","authorization","request"]},a=void 0,l={unversionedId:"components/optional-components/policies",id:"components/optional-components/policies",title:"Policies",description:"Apiato policies are just Laravel Policies,",source:"@site/docs/components/optional-components/policies.md",sourceDirName:"components/optional-components",slug:"/components/optional-components/policies",permalink:"/docs/next/components/optional-components/policies",draft:!1,editUrl:"https://github.com/apiato/documentation/tree/master/docs/components/optional-components/policies.md",tags:[{label:"component",permalink:"/docs/next/tags/component"},{label:"optional-component",permalink:"/docs/next/tags/optional-component"},{label:"policy",permalink:"/docs/next/tags/policy"},{label:"authorization",permalink:"/docs/next/tags/authorization"},{label:"request",permalink:"/docs/next/tags/request"}],version:"current",lastUpdatedBy:"Mohammad Alavi",lastUpdatedAt:1707229167,formattedLastUpdatedAt:"Feb 6, 2024",frontMatter:{title:"Policies",tags:["component","optional-component","policy","authorization","request"]},sidebar:"tutorialSidebar",previous:{title:"Notifications",permalink:"/docs/next/components/optional-components/notifications"},next:{title:"Repositories",permalink:"/docs/next/components/optional-components/repository/repositories"}},s={},p=[{value:"Rules",id:"rules",level:2},{value:"Folder Structure",id:"folder-structure",level:2},{value:"Code Example",id:"code-example",level:2},{value:"Registering Policies",id:"registering-policies",level:2},{value:"Policy Auto-Discovery",id:"policy-auto-discovery",level:3},{value:"Policy Registration Flow",id:"policy-registration-flow",level:2},{value:"Helper Methods",id:"helper-methods",level:2}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,i.c)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"Apiato policies are just ",(0,t.yg)("a",{parentName:"p",href:"https://laravel.com/docs/authorization"},"Laravel Policies"),",\nand they function in the exact same way as Laravel policies.\nHowever, they come with additional rules and conventions specific to Apiato."),(0,t.yg)("p",null,"To generate new policies you may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:policy")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:policy\n")),(0,t.yg)("h2",{id:"rules"},"Rules"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"All Policies:",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"MUST extend the ",(0,t.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Policies\\Policy")," class.",(0,t.yg)("ul",{parentName:"li"},(0,t.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,t.yg)("inlineCode",{parentName:"li"},"ParentPolicy"),"."))),(0,t.yg)("li",{parentName:"ul"},"SHOULD be named after the model they are associated with, followed by the ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy.php"),".")))),(0,t.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,t.yg)("p",null,"The highlighted section showcases the policy registration point:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 UserPolicy.php\n \u2502 \u2514\u2500\u2500 ...\n \u2514\u2500\u2500 Providers\n // highlight-start\n \u251c\u2500\u2500 AuthServiceProvider.php\n // highlight-end\n \u2514\u2500\u2500 ...\n")),(0,t.yg)("h2",{id:"code-example"},"Code Example"),(0,t.yg)("p",null,"Policies are defined exactly as you would define them in Laravel."),(0,t.yg)("h2",{id:"registering-policies"},"Registering Policies"),(0,t.yg)("p",null,"Once the policy class has been created, it needs to be registered.\nRegistering policies is\nhow we can inform Apiato which policy to use when authorizing actions against a given model type."),(0,t.yg)("p",null,"Registering policies can be done\nby adding them to the ",(0,t.yg)("inlineCode",{parentName:"p"},"policies")," array in the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Containers\\{Section}\\{Container}\\Providers\\AuthServiceProvider")," class."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\AuthServiceProvider as ParentAuthProvider;\n\nclass AuthServiceProvider extends ParentAuthProvider\n{\n protected $policies = [\n Post::class => PostPolicy::class,\n ];\n}\n")),(0,t.yg)("p",null,"To generate an event service provider\nyou may use the ",(0,t.yg)("inlineCode",{parentName:"p"},"apiato:generate:provider")," interactive command:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre"},"php artisan apiato:generate:provider\n")),(0,t.yg)("p",null,"Remember to also register the ",(0,t.yg)("inlineCode",{parentName:"p"},"AuthServiceProvider")," in the container's ",(0,t.yg)("inlineCode",{parentName:"p"},"MainServiceProvider"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use ...\nuse App\\Ship\\Parents\\Providers\\MainServiceProvider as ParentMainServiceProvider;\n\nclass MainServiceProvider extends ParentMainServiceProvider\n{\n protected array $serviceProviders = [\n // ... Other service providers\n AuthServiceProvider::class,\n ];\n}\n")),(0,t.yg)("h3",{id:"policy-auto-discovery"},"Policy Auto-Discovery"),(0,t.yg)("p",null,"Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies.\nThis automatic discovery process relies on adhering to standard Apiato naming conventions for policies."),(0,t.yg)("p",null,"By following the ",(0,t.yg)("a",{parentName:"p",href:"#rules"},"rules")," outlined above, you allow Apiato to automatically discover your policies."),(0,t.yg)("p",null,"To summarize:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Policies must be stored within the ",(0,t.yg)("inlineCode",{parentName:"li"},"app/Containers/{section}/{container}/Policies")," directory."),(0,t.yg)("li",{parentName:"ul"},"The policy name should mirror the corresponding model's name while appending a ",(0,t.yg)("inlineCode",{parentName:"li"},"Policy")," suffix. For instance, a ",(0,t.yg)("inlineCode",{parentName:"li"},"User")," model corresponds to a ",(0,t.yg)("inlineCode",{parentName:"li"},"UserPolicy")," policy class.")),(0,t.yg)("h2",{id:"policy-registration-flow"},"Policy Registration Flow"),(0,t.yg)("p",null,"In case you are going to register your policies manually, and don't want to use the auto-discovery feature,\nyou may want to understand the policy registration process.\nHere is a breakdown of the registration flow."),(0,t.yg)("p",null,"Consider the following folder structure:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"app\n\u2514\u2500\u2500 Containers\n \u2514\u2500\u2500 Section\n \u2514\u2500\u2500 Container\n \u251c\u2500\u2500 Policies\n \u2502 \u251c\u2500\u2500 DemoPolicy.php \u2500\u25ba\u2500\u2510\n \u2502 \u2514\u2500\u2500 ... \u2502 \n \u2514\u2500\u2500 Providers \u25bc\n \u251c\u2500\u2500 AuthServiceProvider.php \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u251c\u2500\u2500 MainServiceProvider.php \u25c4\u2500registered\u2500in\u2500\u25c4\u2518\n \u2514\u2500\u2500 ...\n\n")),(0,t.yg)("p",null,"The following diagram illustrates the registration flow of policies in the above folder structure:"),(0,t.yg)("mermaid",{value:"graph LR\n subgraph Container\n MainServiceProvider\n AuthServiceProvider\n DemoPolicy\n end\n \n MainServiceProvider --\x3e|loads| AuthServiceProvider\n AuthServiceProvider --\x3e|registered in| MainServiceProvider\n DemoPolicy --\x3e|registered in| AuthServiceProvider\n\n subgraph Application\n SPLoader[[Service Provider Loader]]-- loads--\x3eMainServiceProvider\n end"}),(0,t.yg)("h2",{id:"helper-methods"},"Helper Methods"),(0,t.yg)("blockquote",null,(0,t.yg)("p",{parentName:"blockquote"},"Available since Core v8.7.0")),(0,t.yg)("p",null,"All models are equipped with the ",(0,t.yg)("inlineCode",{parentName:"p"},"owns")," and ",(0,t.yg)("inlineCode",{parentName:"p"},"isOwnedBy")," methods,\nmade available through the ",(0,t.yg)("inlineCode",{parentName:"p"},"Apiato\\Core\\Traits\\CanOwnTrait")," trait.\nThese methods offer a convenient way to determine if a model is owned by another model or if a model owns another model."),(0,t.yg)("p",null,"These methods support all types of relationships, as demonstrated below:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// Check if a user owns a post\n$user->owns($post);\n\n// Check if a post is owned by a user\n$post->isOwnedBy($user);\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9ba65a7f.1c8ede36.js b/assets/js/9ba65a7f.1c8ede36.js deleted file mode 100644 index 9b5b46aa1..000000000 --- a/assets/js/9ba65a7f.1c8ede36.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[3808],{95788:(e,t,n)=>{n.d(t,{Iu:()=>d,yg:()=>g});var a=n(11504);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 r(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):r(r({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",h={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=o(e,["components","mdxType","originalType","parentName"]),u=p(n),c=i,g=u["".concat(l,".").concat(c)]||u[c]||h[c]||s;return n?a.createElement(g,r(r({ref:t},d),{},{components:n})):a.createElement(g,r({ref:t},d))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,r=new Array(s);r[0]=c;var o={};for(var l in t)hasOwnProperty.call(t,l)&&(o[l]=t[l]);o.originalType=e,o[u]="string"==typeof e?e:i,r[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>o,toc:()=>p});var a=n(45072),i=(n(11504),n(95788));const s={title:"Tests",tags:["component","optional-component","test"]},r=void 0,o={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:1707153375,formattedLastUpdatedAt:"Feb 5, 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:"method",level:4},{value:"auth",id:"method",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},u="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.c)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("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.yg)("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.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.yg)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.yg)("h2",{id:"definitions"},"Definitions"),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"unit-tests"},"Unit tests"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"functional-tests"},"Functional tests"),(0,i.yg)("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.yg)("h2",{id:"rules"},"Rules"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All container-specific tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests")," directory."),(0,i.yg)("li",{parentName:"ul"},"Functional tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional")," directory."),(0,i.yg)("li",{parentName:"ul"},"API tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/API")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\ApiTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))),(0,i.yg)("li",{parentName:"ul"},"CLI tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/CLI")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\CliTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))))),(0,i.yg)("li",{parentName:"ul"},"Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"Directory structure MUST exactly match the Container's directory structure."))))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\ShipTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"ContainerTestCases")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"ShipTestCase")," MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.yg)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.yg)("pre",null,(0,i.yg)("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 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 CLI\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateAdminCommandTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 ApiTestCase.php\n\u2502 \u2502 \u2514\u2500\u2500 CliTestCase.php\n\u2502 \u251c\u2500\u2500 Unit \n\u2502 \u2502 \u251c\u2500\u2500 Actions\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 AnotherDirectory\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 ...\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UI\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u251c\u2500\u2500 ContainerTestCase.php\n\u2502 \u251c\u2500\u2500 FunctionalTestCase.php\n\u2502 \u2514\u2500\u2500 UnitTestCase.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 ShipTestCase.php\n")),(0,i.yg)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\Tests\\Functional\\API;\n\nuse App\\Containers\\AppSection\\User\\Data\\Factories\\UserFactory;\nuse App\\Containers\\AppSection\\User\\Tests\\Functional\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\nuse PHPUnit\\Framework\\Attributes\\CoversNothing;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('user')]\n#[CoversNothing]\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.yg)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.yg)("h2",{id:"functional-test-helpers"},"Functional Test Helpers"),(0,i.yg)("p",null,"Apiato provides a set of helper methods that you can use to write expressive functional tests."),(0,i.yg)("h3",{id:"properties"},"Properties"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"endpoint"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.yg)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"auth"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.yg)("p",null,"In the context of testing, when ",(0,i.yg)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"access"},"access"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("p",null,"By setting the desired roles and permissions in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("h3",{id:"methods"},"Methods"),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguser"},"getTestingUser"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#endpoint-method"},"endpoint"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#auth-method"},"auth")," "),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"makecall"},"makeCall"),(0,i.yg)("p",null,"To make a request to your application, you may invoke the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.yg)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"injectid"},"injectId"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this example, the original endpoint is ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.yg)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.yg)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.yg)("p",null,"By default, ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"When the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.yg)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"By utilizing the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.yg)("p",null,"When you call ",(0,i.yg)("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.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.yg)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.yg)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"The order in which you call ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.yg)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.yg)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.yg)("p",null,"Here's an example of how to use ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.yg)("p",null,"In the code snippet above, ",(0,i.yg)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.yg)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.yg)("p",null,"In this case, the assertion will ignore the ",(0,i.yg)("inlineCode",{parentName:"p"},"id"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.yg)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.yg)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.yg)("p",null,"By using the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.yg)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.yg)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"getgatemock"},"getGateMock"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available in v12.1.0 and above.")),(0,i.yg)("p",null,"This assertion helps you to test whether the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.yg)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this code, we're examining the testing of the ",(0,i.yg)("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.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"inids"},"inIds"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.yg)("p",null,"By leveraging the ",(0,i.yg)("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.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("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.yg)("h2",{id:"faker"},"Faker"),(0,i.yg)("p",null,"An instance of ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.yg)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.yg)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.yg)("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.yg)("p",null,"To create your live testing data, navigate to the ",(0,i.yg)("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.yg)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.yg)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9ba65a7f.e6112df6.js b/assets/js/9ba65a7f.e6112df6.js new file mode 100644 index 000000000..e800da981 --- /dev/null +++ b/assets/js/9ba65a7f.e6112df6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocumentation=self.webpackChunkdocumentation||[]).push([[3808],{95788:(e,t,n)=>{n.d(t,{Iu:()=>d,yg:()=>g});var a=n(11504);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 r(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):r(r({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",h={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=o(e,["components","mdxType","originalType","parentName"]),u=p(n),c=i,g=u["".concat(l,".").concat(c)]||u[c]||h[c]||s;return n?a.createElement(g,r(r({ref:t},d),{},{components:n})):a.createElement(g,r({ref:t},d))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var s=n.length,r=new Array(s);r[0]=c;var o={};for(var l in t)hasOwnProperty.call(t,l)&&(o[l]=t[l]);o.originalType=e,o[u]="string"==typeof e?e:i,r[1]=o;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>s,metadata:()=>o,toc:()=>p});var a=n(45072),i=(n(11504),n(95788));const s={title:"Tests",tags:["component","optional-component","test"]},r=void 0,o={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:1707229167,formattedLastUpdatedAt:"Feb 6, 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 Tests",id:"functional-tests-1",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:"method",level:4},{value:"auth",id:"method",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:"assertCriteriaPushedToRepository",id:"assertcriteriapushedtorepository",level:4},{value:"assertNoCriteriaPushedToRepository",id:"assertnocriteriapushedtorepository",level:4},{value:"allowAddRequestCriteriaInvocation",id:"allowaddrequestcriteriainvocation",level:4},{value:"Faker",id:"faker",level:2},{value:"Test Helper Methods",id:"test-helper-methods",level:2},{value:"createSpyWithRepository",id:"createspywithrepository",level:4},{value:"inIds",id:"inids",level:4},{value:"Create Live Testing Data",id:"create-live-testing-data",level:2}],d={toc:p},u="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.c)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("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.yg)("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.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests"},"HTTP tests")," for more information on the available testing methods."),(0,i.yg)("p",null,"To generate new tests you may use the following interactive commands:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:generate:test:unit\nphp artisan apiato:generate:test:functional\nphp artisan apiato:generate:test:testcase\n")),(0,i.yg)("h2",{id:"definitions"},"Definitions"),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"unit-tests"},"Unit tests"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"functional-tests"},"Functional tests"),(0,i.yg)("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.yg)("h2",{id:"rules"},"Rules"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All container-specific tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests")," directory."),(0,i.yg)("li",{parentName:"ul"},"Functional tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional")," directory."),(0,i.yg)("li",{parentName:"ul"},"API tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/API")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\ApiTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))),(0,i.yg)("li",{parentName:"ul"},"CLI tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Functional/CLI")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\Functional\\CliTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\FunctionalTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))))))))),(0,i.yg)("li",{parentName:"ul"},"Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Containers/{Section}/{Container}/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\UnitTestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Containers\\{Section}\\{Container}\\Tests\\ContainerTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"Directory structure MUST exactly match the Container's directory structure."))))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"Ship")," Unit tests:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"MUST be placed in the ",(0,i.yg)("inlineCode",{parentName:"li"},"app/Ship/Tests/Unit")," directory."),(0,i.yg)("li",{parentName:"ul"},"MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Tests\\ShipTestCase")," class."))),(0,i.yg)("li",{parentName:"ul"},"All ",(0,i.yg)("inlineCode",{parentName:"li"},"ContainerTestCases")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"ShipTestCase")," MUST extend the ",(0,i.yg)("inlineCode",{parentName:"li"},"App\\Ship\\Parents\\Tests\\PhpUnit\\TestCase")," class.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"The parent extension SHOULD be aliased as ",(0,i.yg)("inlineCode",{parentName:"li"},"ParentTestCase"),".")))),(0,i.yg)("h2",{id:"folder-structure"},"Folder Structure"),(0,i.yg)("pre",null,(0,i.yg)("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 \u2514\u2500\u2500 Tests\n\u2502 \u251c\u2500\u2500 Functional\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 CLI\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateAdminCommandTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 ApiTestCase.php\n\u2502 \u2502 \u2514\u2500\u2500 CliTestCase.php\n\u2502 \u251c\u2500\u2500 Unit \n\u2502 \u2502 \u251c\u2500\u2500 Actions\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 CreateUserActionTest.php\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u251c\u2500\u2500 AnotherDirectory\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 ...\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 UI\n\u2502 \u2502 \u251c\u2500\u2500 API\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 WEB\n\u2502 \u2502 \u251c\u2500\u2500 Controllers\n\u2502 \u2502 \u251c\u2500\u2500 Requests\n\u2502 \u2502 \u251c\u2500\u2500 Transformers\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u251c\u2500\u2500 ContainerTestCase.php\n\u2502 \u251c\u2500\u2500 FunctionalTestCase.php\n\u2502 \u2514\u2500\u2500 UnitTestCase.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 ShipTestCase.php\n")),(0,i.yg)("h2",{id:"writing-tests"},"Writing Tests"),(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Containers\\AppSection\\User\\Tests\\Functional\\API;\n\nuse App\\Containers\\AppSection\\User\\Data\\Factories\\UserFactory;\nuse App\\Containers\\AppSection\\User\\Tests\\Functional\\ApiTestCase;\nuse Illuminate\\Testing\\Fluent\\AssertableJson;\nuse PHPUnit\\Framework\\Attributes\\CoversNothing;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('user')]\n#[CoversNothing]\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.yg)("p",null,"To learn more about the properties and methods used,\nsuch as ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall"),", please read to the following section."),(0,i.yg)("h2",{id:"functional-tests-1"},"Functional Tests"),(0,i.yg)("h3",{id:"properties"},"Properties"),(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"endpoint"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"$endpoint")," property is used\nto define the endpoints you want to access when making a call using the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method.\nIt is defined as a string in the following format: ",(0,i.yg)("inlineCode",{parentName:"p"},"method@url"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"auth"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property in your test class, it will be defaulted to ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," automatically."),(0,i.yg)("p",null,"In the context of testing, when ",(0,i.yg)("inlineCode",{parentName:"p"},"auth")," is set to true,\nthe ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"access"},"access"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser")," method,\nthe testing user instance will automatically inherit all the roles and permissions specified in the ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("p",null,"By setting the desired roles and permissions in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("h3",{id:"methods"},"Methods"),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#makecall"},"makeCall"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#injectid"},"injectId"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguser"},"getTestingUser"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#endpoint-method"},"endpoint"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#auth-method"},"auth")," "),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"makecall"},"makeCall"),(0,i.yg)("p",null,"To make a request to your application, you may invoke the ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method within your functional test.\nThis method combines the functionalities of ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/http-tests#testing-json-apis"},"Laravel HTTP test")," helpers with the ",(0,i.yg)("a",{parentName:"p",href:"#properties"},"properties"),"\ndefined in your functional test to make a request to the application."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"makeCall")," method returns an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Illuminate\\Testing\\TestResponse"),",\nwhich provides a variety of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"injectid"},"injectId"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method enables you to inject an ID into the endpoint you want to test within your functional tests."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this example, the original endpoint is ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/{id}'"),", and the desired ID to be injected is ",(0,i.yg)("inlineCode",{parentName:"p"},"100"),".\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method is then called with these parameters.\nThe method replaces ",(0,i.yg)("inlineCode",{parentName:"p"},"{id}")," in the endpoint with the provided ID,\nresulting in the modified endpoint ",(0,i.yg)("inlineCode",{parentName:"p"},"'get@v1/users/100'"),"."),(0,i.yg)("p",null,"By default, ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId"),"\nwill look for a string of ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"When the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/security/hash-id"},"Hash ID")," feature is enabled,\nthe ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"injectId")," method,\n",(0,i.yg)("inlineCode",{parentName:"p"},"skipEncoding"),"."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"By utilizing the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguser"},"getTestingUser"),(0,i.yg)("p",null,"When you call ",(0,i.yg)("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.yg)("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.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"Additionally, to create an admin user, you can pass ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," as the third argument when invoking ",(0,i.yg)("inlineCode",{parentName:"p"},"getTestingUser"),".\nThis will use the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," state of ",(0,i.yg)("inlineCode",{parentName:"p"},"app/Containers/AppSection/User/Data/Factories/UserFactory.php")," to create the testing user."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUser(null, null, true);\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"tests")," configuration in ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"gettestinguserwithoutaccess"},"getTestingUserWithoutAccess"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"getTestingUser(null, null)"),".\nThis skips all the roles and permissions defined in your test class ",(0,i.yg)("inlineCode",{parentName:"p"},"$access")," property."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$user = $this->getTestingUserWithoutAccess();\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"endpoint"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$endpoint")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->endpoint('get@v1/register')->makeCall();\n")),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"The order in which you call ",(0,i.yg)("inlineCode",{parentName:"p"},"endpoint")," method is crucial.\nMake sure to call it before ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," method,\nor else ",(0,i.yg)("inlineCode",{parentName:"p"},"injectId")," will not replace the ID in the overridden endpoint.")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"method"},"auth"),(0,i.yg)("p",null,"The ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$auth")," property of the test class,\nspecifically for a particular test method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->auth(false)->makeCall();\n")),(0,i.yg)("h2",{id:"available-assertions"},"Available Assertions"),(0,i.yg)("p",null,"Apiato provides a variety of custom assertion methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#assertModelCastsIsEmpty"},"assertModelCastsIsEmpty"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertDatabaseTable"},"assertDatabaseTable"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#getGateMock"},"getGateMock"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertcriteriapushedtorepository"},"assertCriteriaPushedToRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#assertnocriteriapushedtorepository"},"assertNoCriteriaPushedToRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#allowaddrequestcriteriainvocation"},"allowAddRequestCriteriaInvocation")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertmodelcastsisempty"},"assertModelCastsIsEmpty"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method allows you to assert that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model is empty.\nBy default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of a model includes the ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and,\nif the model is soft deletable, the ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),".\nThis method excludes these default values from the assertion."),(0,i.yg)("p",null,"Here's an example of how to use ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model);\n")),(0,i.yg)("p",null,"In the code snippet above, ",(0,i.yg)("inlineCode",{parentName:"p"},"$model")," represents the instance of the model you want to test.\nThe ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method will verify that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model is empty,\nignoring the default ",(0,i.yg)("inlineCode",{parentName:"p"},"id")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at")," values."),(0,i.yg)("p",null,"If you want to add additional values to the ignore list,\nyou can pass them as an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertModelCastsIsEmpty($model, ['value1', 'value2']);\n")),(0,i.yg)("p",null,"In this case, the assertion will ignore the ",(0,i.yg)("inlineCode",{parentName:"p"},"id"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"deleted_at"),",\n",(0,i.yg)("inlineCode",{parentName:"p"},"value1"),", and ",(0,i.yg)("inlineCode",{parentName:"p"},"value2")," values when verifying the ",(0,i.yg)("inlineCode",{parentName:"p"},"$casts")," property of the model."),(0,i.yg)("p",null,"By using the ",(0,i.yg)("inlineCode",{parentName:"p"},"assertModelCastsIsEmpty")," method,\nyou can verify that the ",(0,i.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertdatabasetable"},"assertDatabaseTable"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.5.0")),(0,i.yg)("p",null,"This method is used\nto verify\nif the database table specified by ",(0,i.yg)("inlineCode",{parentName:"p"},"table")," has the expected columns specified in the ",(0,i.yg)("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.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$this->assertDatabaseTable('users', ['id' => 'bigint']);\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"getgatemock"},"getGateMock"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.5.0")),(0,i.yg)("p",null,"This assertion helps you to test whether the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method is invoked with the correct arguments."),(0,i.yg)("p",null,"Let's\nconsider a scenario\nwhere a request class utilizes the ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"authorize")," method correctly invokes the ",(0,i.yg)("inlineCode",{parentName:"p"},"Gate::allows")," method with the appropriate arguments."),(0,i.yg)("pre",null,(0,i.yg)("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.yg)("p",null,"In this code, we're examining the testing of the ",(0,i.yg)("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.yg)("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.yg)("hr",null),(0,i.yg)("h4",{id:"assertcriteriapushedtorepository"},"assertCriteriaPushedToRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Asserts that a criteria is pushed to a repository."),(0,i.yg)("p",null,"In the following example, we want to test\nif the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserIsAdminCriteria")," is pushed to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserRepository")," when the ",(0,i.yg)("inlineCode",{parentName:"p"},"ListUsersTask")," is called with the ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," method."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAdminUsers(): void\n{\n $this->assertCriteriaPushedToRepository(\n UserRepository::class,\n UserIsAdminCriteria::class,\n ['admin' => true],\n );\n $task = app(ListUsersTask::class);\n\n $task->admin();\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"assertnocriteriapushedtorepository"},"assertNoCriteriaPushedToRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Asserts that no criteria is pushed to a repository."),(0,i.yg)("p",null,"In the following example, we want to test\nif no criteria is pushed to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserRepository")," when the ",(0,i.yg)("inlineCode",{parentName:"p"},"ListUsersTask"),"'s ",(0,i.yg)("inlineCode",{parentName:"p"},"admin")," method is called with a ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," value."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAllUsers(): void\n{\n $this->assertNoCriteriaPushedToRepository(UserRepository::class);\n $task = app(ListUsersTask::class);\n\n $task->admin(null);\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"allowaddrequestcriteriainvocation"},"allowAddRequestCriteriaInvocation"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"Allow ",(0,i.yg)("inlineCode",{parentName:"p"},"addRequestCriteria")," method invocation on the repository mock.\nThis is particularly useful when you want to test a repository that uses the\n",(0,i.yg)("a",{parentName:"p",href:"/docs/next/components/optional-components/repository/repositories#requestcriteria"},"RequestCriteria"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanListAdminUsers(): void\n{\n $repositoryMock = $this->assertCriteriaPushedToRepository(\n UserRepository::class,\n UserIsAdminCriteria::class,\n ['admin' => true],\n );\n // highlight-next-line\n $this->allowAddRequestCriteriaInvocation($repositoryMock);\n $task = app(ListUsersTask::class);\n\n $task->admin();\n}\n")),(0,i.yg)("h2",{id:"faker"},"Faker"),(0,i.yg)("p",null,"An instance of ",(0,i.yg)("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.yg)("inlineCode",{parentName:"p"},"$this->faker"),"."),(0,i.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This feature is deprecated and will be removed in the next major release.\nYou should use the Laravel ",(0,i.yg)("inlineCode",{parentName:"p"},"fake")," helper function instead.")),(0,i.yg)("h2",{id:"test-helper-methods"},"Test Helper Methods"),(0,i.yg)("p",null,"Apiato provides a variety of helper methods that you may utilize when testing your application."),(0,i.yg)("p",null,(0,i.yg)("a",{parentName:"p",href:"#createspywithrepository"},"createSpyWithRepository"),(0,i.yg)("br",{parentName:"p"}),"\n",(0,i.yg)("a",{parentName:"p",href:"#inIds"},"inIds")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"createspywithrepository"},"createSpyWithRepository"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"Available since Core v8.9.0")),(0,i.yg)("p",null,"This method is useful when you want to test if a repository method is called within an Action, SubAction or a Task."),(0,i.yg)("p",null,"In the following example,\nwe want to test if the ",(0,i.yg)("inlineCode",{parentName:"p"},"run")," method of the ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserTask")," is called within the ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserAction"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"public function testCanCreateUser(): void\n{\n $data = [\n 'email' => 'gandalf@the.grey',\n 'password' => 'you-shall-not-pass',\n ];\n $taskSpy = $this->createSpyWithRepository(CreateUserTask::class, UserRepository::class);\n $action = app(CreateUserAction::class);\n\n $action->run($data);\n \n $taskSpy->shouldHaveReceived('run')->once()->with($data);\n}\n")),(0,i.yg)("hr",null),(0,i.yg)("h4",{id:"inids"},"inIds"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"inIds")," method allows you to check if the given hashed ID exists within the provided model collection."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$hashedId = 'hashed_123';\n$collection = Model::all();\n\n$isInCollection = $this->inIds($hashedId, $collection);\n")),(0,i.yg)("p",null,"By leveraging the ",(0,i.yg)("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.yg)("admonition",{title:"Deprecation Notice",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},"This method will be removed in the next major release and will not be available in test classes.")),(0,i.yg)("h2",{id:"create-live-testing-data"},"Create Live Testing Data"),(0,i.yg)("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.yg)("p",null,"To create your live testing data, navigate to the ",(0,i.yg)("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.yg)("p",null,"Once you have defined your testing data,\nyou can run the following command in your terminal:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"php artisan apiato:seed-test\n")),(0,i.yg)("p",null,"This command triggers the seeding process specifically for testing data,\npopulating your application with the generated data."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.1a749a80.js b/assets/js/runtime~main.35c54bbc.js similarity index 98% rename from assets/js/runtime~main.1a749a80.js rename to assets/js/runtime~main.35c54bbc.js index 87d08d0a4..0c7073987 100644 --- a/assets/js/runtime~main.1a749a80.js +++ b/assets/js/runtime~main.35c54bbc.js @@ -1 +1 @@ -(()=>{"use strict";var e,c,a,f,b,d={},t={};function r(e){var c=t[e];if(void 0!==c)return c.exports;var a=t[e]={exports:{}};return d[e].call(a.exports,a,a.exports,r),a.exports}r.m=d,e=[],r.O=(c,a,f,b)=>{if(!a){var d=1/0;for(i=0;i=b)&&Object.keys(r.O).every((e=>r.O[e](a[o])))?a.splice(o--,1):(t=!1,b0&&e[i-1][2]>b;i--)e[i]=e[i-1];e[i]=[a,f,b]},r.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return r.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var b=Object.create(null);r.r(b);var d={};c=c||[null,a({}),a([]),a(a)];for(var t=2&f&&e;"object"==typeof t&&!~c.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach((c=>d[c]=()=>e[c]));return d.default=()=>e,r.d(b,d),b},r.d=(e,c)=>{for(var a in c)r.o(c,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((c,a)=>(r.f[a](e,c),c)),[])),r.u=e=>"assets/js/"+({64:"10691446",66:"d85472ef",68:"986c3199",76:"8c9fccb5",82:"5a824d24",88:"245df310",127:"af8bcf34",192:"ad753fe5",204:"fbb5ccd8",224:"3a8a9a02",240:"31a3eb6f",276:"20bb4ef4",300:"ed111213",304:"2c8be122",308:"77c0bb11",340:"2c32570e",384:"a29806db",388:"53f60aaa",392:"abe60d8e",400:"f3323e34",464:"245b95ea",532:"1675a853",556:"ffafab28",576:"f4ed0a4d",592:"41e9fc02",608:"add102e2",640:"63cffe9f",716:"c6d7e964",726:"9dd4b0ef",760:"c21f0827",780:"b8242e5d",784:"19031211",792:"4683e51a",804:"bfdf68d4",824:"e5c09629",836:"5040bcd3",866:"a765b786",940:"db5219b9",984:"a291d650",1028:"0c2c09cf",1040:"a55de8d9",1044:"6855260d",1144:"d6d55995",1152:"4ce02702",1156:"4ee994fc",1236:"7c71f28e",1260:"38a9fada",1264:"114e21b0",1360:"2529077f",1380:"9f93909f",1424:"03d23ece",1448:"3c75101b",1504:"3919a682",1512:"fdca9218",1532:"ff4e10c4",1538:"ec989c0b",1580:"321c3473",1632:"1c74e866",1640:"69995e4b",1724:"0bfb57e8",1804:"fcdb2c3c",1808:"1958aa06",1824:"77aa4d93",1860:"10f6dc74",1912:"1ebbf8ad",1932:"023af9ff",1960:"fc7b1962",2e3:"0d59295f",2008:"2a5e16ec",2020:"31910907",2030:"8013ae06",2032:"93d36f00",2068:"53ff84c0",2134:"303f2916",2140:"360898bf",2152:"1d2ace06",2164:"e848ef61",2168:"8b4b94e0",2180:"d3ed5dd0",2208:"1db39f58",2224:"4b4e43c1",2240:"540a99bf",2252:"a8497d64",2280:"6459b84b",2288:"c8555b7b",2324:"dbdbfb09",2352:"149faf5c",2360:"b5a6ea24",2392:"99ff9a84",2492:"02e9e60d",2496:"abba3c1a",2504:"38e3e5e2",2508:"3c8cc128",2514:"f981018f",2520:"02dc5f66",2528:"0fdfb154",2540:"e08582aa",2548:"6ca79f64",2556:"92611ec4",2560:"33939c05",2620:"abd7c6e1",2632:"c4f5d8e4",2692:"99dcebb4",2704:"a193a73b",2724:"c8795c05",2728:"28b502c2",2760:"128066bb",2788:"4504fa21",2804:"66b63dfc",2813:"54071611",2844:"630dcb4d",2856:"b626b9c9",2860:"c417dca4",2900:"36ca3315",2904:"892cf132",2908:"aaf1cba5",2956:"9c612400",2968:"7b4e6d79",2988:"a10ffd78",3072:"427fd2ae",3088:"15e12a2f",3096:"1c647d6d",3104:"90c91afe",3116:"a7d0887a",3128:"9fcd2aaf",3140:"92ea068e",3164:"ace976d6",3168:"7c884429",3184:"b2c1c78c",3188:"66d590de",3240:"6e7ac859",3264:"6095db2e",3268:"e8f086b4",3284:"60321f1e",3300:"9ec0fefb",3330:"8e61a622",3336:"47a03c7f",3340:"2f4205f9",3348:"effa6628",3400:"df203c0f",3404:"57aa3e64",3408:"06abd5b6",3436:"934030ed",3440:"b7b5f722",3448:"9f7214f0",3512:"e63fc1b2",3548:"1746ec49",3564:"aa3c5b40",3638:"1c2e3411",3656:"d72ff326",3662:"40d100db",3668:"fd1ff340",3676:"dbe067dd",3732:"3dd44646",3752:"45778f4a",3764:"18ee9ead",3800:"0b3ceb4d",3808:"9ba65a7f",3872:"67ad9935",3876:"c02ab877",3878:"42656b86",3884:"82d76014",3888:"b9d99630",3936:"da8b188d",3944:"0a783a8b",3972:"2e6078dc",4006:"f2b76d46",4028:"d8325a2c",4040:"cc5c6d64",4050:"76f67042",4088:"20702474",4108:"d4124539",4128:"33f68dcb",4164:"7c142331",4184:"9d6a5804",4196:"70b24a51",4235:"f119515c",4236:"2af38141",4272:"a247f8ec",4296:"55960ee5",4300:"2f6f9645",4304:"31874089",4376:"75d21731",4412:"4d3c06b1",4416:"4a69c104",4424:"5ecea96b",4432:"ef449f35",4436:"7c87e584",4443:"92b2ca3c",4464:"491bc18c",4466:"819f8db2",4492:"3720c009",4536:"84164204",4540:"332d52b4",4624:"b4ea3a21",4664:"c3ec0a52",4700:"956e9f49",4704:"693f6ee0",4740:"374b0855",4772:"27c465e4",4776:"40185eb5",4820:"3fa6a9b5",4888:"cf3ee67c",4891:"aca4b983",4901:"87d519de",4920:"fe4c7294",4932:"9650c219",4936:"27299a3b",4944:"9cc140f3",4950:"54828236",4956:"b5a0697e",4964:"9522a145",4968:"a49f1700",4984:"e1c5dcea",4992:"c4cc7857",5016:"e8ac06ec",5108:"1626590c",5120:"05614d2f",5160:"c4023386",5203:"35891e33",5222:"b50f730b",5244:"30644fd9",5248:"b3eefa80",5308:"dae7336e",5316:"4015baac",5328:"2e3626b2",5344:"13f612ac",5420:"8882a989",5456:"826b2a47",5464:"ec13b678",5488:"4a272307",5496:"2457aad7",5544:"2f1df01f",5566:"8fce0176",5572:"7d1f259c",5596:"ef04c7c8",5606:"df6a485b",5620:"f1acbeca",5631:"eb3c6d3c",5660:"881a9bc1",5674:"b221159f",5696:"935f2afb",5728:"2ac485cd",5748:"0d75c27e",5752:"eeb62e5b",5800:"4890df88",5816:"28451e1a",5836:"c2998ab0",5882:"687b8652",5892:"30dc594d",5928:"1f987ab0",5976:"8c90118e",6e3:"c51d05bf",6011:"13d06b8c",6020:"5e76b55a",6092:"47355b93",6136:"8e0e9e2b",6176:"bacc2700",6180:"33b191bc",6262:"9cd87160",6266:"b1c5cbf2",6284:"89918c0c",6320:"23c8336c",6419:"7458314f",6428:"53df34ca",6442:"5ba01c12",6513:"de0ecdfa",6560:"1b1d605d",6568:"f187f30c",6580:"3ffc09c4",6668:"18c33990",6692:"cf7e6749",6720:"0e1a79a5",6724:"6127c255",6752:"17896441",6764:"c16bfc7e",6768:"834e3df2",6796:"8dd1624d",6808:"d055169c",6860:"2a15d269",6864:"5ea00916",6872:"db5d8c25",6876:"4d7c6870",6904:"1647ec57",6956:"28055a57",6964:"5e923e9d",6976:"144669f6",6992:"53fc899c",7040:"31053c79",7108:"450e6318",7110:"738bceb6",7120:"4e202c12",7144:"da105351",7164:"f0ea2bc1",7184:"963bb5ab",7204:"e8e0ef40",7208:"d52025df",7222:"7b0d43d2",7228:"d34bd87a",7243:"fd3f6a3d",7244:"9c985f15",7256:"4bf0522b",7272:"1b07933a",7280:"a1a6e4a2",7312:"959be4b4",7320:"f6807fb4",7332:"98999a3e",7336:"22ecef17",7348:"5ac833b0",7384:"d994236d",7412:"2b026185",7428:"653187f9",7432:"29d4a56b",7440:"0d8578a6",7456:"b65ccb97",7492:"327f2b8d",7516:"4d0c2aca",7540:"a63c68ec",7556:"bd783ed9",7604:"4d96365a",7608:"44a2c628",7688:"3b074962",7720:"8f47f31d",7736:"4610191c",7764:"d5f37c55",7776:"59ee4ecb",7792:"ddec9574",7820:"dae33245",7824:"69bce674",7844:"9bbc65ac",7868:"26084ccc",7872:"f1121274",7968:"3d9aab4f",7988:"28a91100",8008:"c7c4bc80",8040:"fa1390c3",8044:"61086f7d",8056:"9e660ab4",8112:"415f74f4",8120:"96944d2a",8140:"9944c6d1",8180:"3f06413e",8212:"6777c82a",8216:"deb44a68",8264:"14935a59",8268:"27268bf4",8276:"ecd5baba",8292:"8375e767",8368:"e5ac4d56",8376:"8ecb0387",8388:"f32194d4",8392:"4707ca8c",8403:"c3fa5730",8436:"af738211",8476:"a71c172d",8487:"13140ae3",8488:"518c31a4",8502:"62e78712",8532:"018bfed5",8564:"df8fe5f1",8592:"2ddf079f",8620:"525560b7",8632:"55ea0897",8666:"86b21c86",8756:"01255979",8800:"1b728867",8808:"ef35623d",8848:"0688ea12",8888:"7ced6537",8980:"3ce8e004",9e3:"76827885",9008:"9840276f",9020:"92ca3f12",9072:"93746dcf",9076:"94056865",9146:"f7bd22f2",9160:"1f4f2990",9188:"411cd95c",9192:"db1fb617",9220:"19bbdee1",9232:"7dbacb84",9240:"aa32d378",9248:"7768a617",9260:"9e0bb3f7",9300:"2f3ae6a8",9344:"3b382b4c",9380:"9465b6bc",9384:"fba107f3",9404:"df59c461",9424:"4047e3d8",9450:"044a7006",9480:"0aef6e2f",9496:"1abe76cc",9516:"dbbc6b2c",9540:"a8254ede",9548:"b2aa2b43",9568:"70943eb8",9572:"a8bcf301",9620:"bb0f6255",9624:"329f7802",9632:"cb5f486b",9656:"1be78505",9684:"c1bf43f6",9700:"2aaa4378",9720:"bb32ce55",9724:"58f8f5d8",9728:"bab8e2ef",9752:"a4ce67ea",9760:"1d502ec8",9764:"43ff30f2",9784:"1fa6cfaa",9890:"37833312",9936:"568e42b9",9958:"136a2cf4",9976:"bddd0f35",9988:"94009283",9998:"6aabc5ba"}[e]||e)+"."+{64:"e03d843e",66:"a2a3175d",68:"7c140542",76:"2f3d9000",82:"2a5423d2",88:"f7d4bf9d",127:"1d6a9505",148:"ce04d052",192:"fd6826d6",204:"ad77b4ec",224:"7772fd45",240:"b6868629",276:"b7de9d84",300:"cccd7d35",304:"fbf95519",308:"671143b6",340:"318f27cc",384:"498ba1c0",388:"56f0cb36",392:"35bdac05",400:"4396eb91",464:"aa166608",532:"0381da54",556:"b33d6a55",576:"0d707963",592:"2f1afd17",608:"e5e0fd43",640:"aba14ab8",716:"f5900e0e",726:"f197447c",760:"0e887fd3",780:"d0c6391c",784:"bd623f85",792:"61e31e0f",804:"8b672dfc",824:"fbdc73f1",836:"9cd0536c",866:"a4dbd854",940:"35d55f91",984:"182e8ab7",1028:"0eb1472f",1040:"f29019cb",1044:"ce14a136",1144:"32ab05a6",1152:"9920f714",1156:"0b95df45",1236:"f021ef13",1260:"9afecba6",1264:"bf69cc0e",1360:"a3662a76",1380:"27eb7eb5",1424:"58d6faa5",1448:"0791c839",1504:"230ce4cf",1512:"8a41043d",1532:"dc911aa0",1538:"3bdc0b28",1580:"6c016d82",1632:"208267e7",1640:"837e4187",1676:"b182d0d6",1724:"5792d227",1804:"5480251d",1808:"ab36ddc0",1824:"51db2f08",1848:"f5e87e7f",1860:"5a72fd3f",1912:"643ce78d",1932:"ee07d96e",1960:"1594c6f2",2e3:"699756de",2008:"3aaf1f24",2020:"9c57aed8",2030:"5e749070",2032:"9389e9bf",2068:"781a3f32",2134:"ad02c85e",2140:"942ca312",2152:"872dae6c",2164:"84cb0b3c",2168:"d285c023",2180:"3c1a886a",2208:"addaff04",2224:"e351b1e9",2240:"bbb57199",2252:"2abf6129",2280:"257a7b0e",2288:"26026ac3",2324:"e10dcb78",2352:"96897cd4",2360:"a30bb4e7",2392:"4ca1309f",2492:"94dc7312",2496:"bd1c8254",2504:"764c53b0",2508:"8dcc6b4b",2514:"cdb6dc50",2520:"a9450ec4",2528:"92d21cb6",2540:"6c562438",2548:"237fe638",2556:"cbdd35f2",2560:"f2391da7",2592:"f79bfc25",2620:"a49e91d2",2632:"9e99648f",2692:"302f60c4",2704:"29b855cd",2724:"4c24023b",2728:"1130f732",2760:"9a294ff5",2788:"e4f7a7d3",2804:"125db56d",2813:"fdad7756",2844:"5b53eb95",2856:"1fe563d7",2860:"0540c3dc",2900:"314f4d1e",2904:"414c2fba",2908:"9899e33d",2956:"bccbe11f",2968:"0990cec0",2988:"706b078c",3072:"6a03a665",3088:"dd7086b6",3096:"935fdd63",3104:"b6b9a45d",3116:"abb8d70b",3128:"158d4028",3140:"860a029e",3164:"2f79d741",3168:"0909b306",3184:"f7aedccd",3188:"93f0783d",3240:"414ed64e",3264:"b392a7b5",3268:"b1f67458",3284:"3c85b27d",3300:"95181847",3330:"79061c76",3336:"821a5c56",3340:"cebf54b5",3348:"6be50f37",3400:"a1d2b02a",3404:"a4bdb4d0",3408:"8cf76dba",3436:"f2ea5a74",3440:"a897bd73",3448:"cb396bc4",3512:"34d0593e",3548:"d06b6769",3564:"fed6dfcf",3638:"a1e97033",3656:"f11edc6f",3662:"cb95af95",3668:"03ec2f2e",3676:"e7848cf0",3732:"53479fd9",3752:"d9ff558f",3764:"b9026d53",3800:"dfb95537",3808:"1c8ede36",3872:"08497ea6",3876:"bd68bdeb",3878:"3312c963",3884:"1ba336e9",3888:"1bf95994",3936:"0951d6c3",3944:"be235f34",3972:"895e18e8",4006:"b60f28ef",4028:"6c371536",4040:"35b13b21",4050:"2fcd8697",4088:"68f7594b",4108:"d0e7efcb",4128:"425cd3d1",4164:"61a1611b",4184:"cf4b1a8b",4196:"d6a9cb1c",4235:"8aa4f1f8",4236:"039d38db",4272:"ff3c3554",4296:"f8f72c84",4300:"5abb1c55",4304:"cfdd4711",4376:"f295987b",4412:"8c636985",4416:"03c614df",4424:"49d57c0a",4432:"9fb2e433",4436:"2708aaf1",4443:"41ed0f79",4464:"8453a132",4466:"60d25c4d",4492:"057ff55e",4536:"6a531817",4540:"4e4cb182",4624:"cf2cd490",4664:"55f90633",4700:"166464d2",4704:"e1fe6f49",4740:"4950ec81",4772:"4f6c8e90",4776:"b4aa1eca",4820:"b5a6b9f9",4888:"82ae85c2",4891:"a3b3efa7",4901:"ac12d733",4920:"8beb6b75",4932:"813cbebd",4936:"93f5a6c6",4944:"88fd156f",4950:"5ae5254a",4956:"3aad476d",4964:"357238f0",4968:"6b974736",4984:"24dc9b4d",4992:"a9f4c793",5016:"5700b32f",5108:"8f5f9a80",5120:"ee6b3e70",5160:"9abfb881",5203:"37464699",5222:"b4b2f3ae",5244:"5758235c",5248:"b3c460a0",5308:"79fa30cd",5316:"6b98909d",5328:"5a9e1a76",5344:"d645b040",5420:"bc1f28ba",5456:"615fb125",5464:"47ac9494",5488:"b13f013e",5496:"3865d741",5544:"8720b9ef",5566:"366af879",5572:"3cd92a5f",5596:"1b2409bd",5606:"ddad1a20",5620:"950a6f83",5631:"59487b1d",5660:"c7c44aae",5674:"ea702f9c",5696:"f1eb8702",5728:"60b84ce1",5748:"846988bb",5752:"e08db8e5",5800:"1c683c72",5816:"ee085898",5836:"0664c43a",5882:"fd7d220b",5892:"d937092d",5928:"236cc305",5976:"10833c6f",6e3:"9973b90b",6011:"deea3b76",6020:"38044a2a",6092:"470a4b8e",6136:"72d78faa",6176:"3514dd94",6180:"7ead1419",6262:"d0f1292f",6266:"c89a7d60",6284:"5d47c466",6320:"41ddab3c",6419:"31fba5b3",6428:"f7d3ad96",6442:"fd5b9d86",6513:"ef782f41",6560:"4a6c51d5",6568:"400f22f2",6579:"5388cc08",6580:"c2453e03",6668:"4ddc7406",6692:"901f95da",6720:"8a9f16a1",6724:"06724ddb",6752:"dd61cb89",6764:"a0617972",6768:"8bb4739b",6796:"fbf17ae0",6808:"400e7bda",6860:"7a363877",6864:"102e1842",6872:"7f1d9688",6876:"7cc387d7",6904:"77f851e7",6956:"5a1f67d4",6964:"0cecdb26",6976:"226d4280",6992:"a530b4b0",7040:"d43d962b",7108:"ca99a9e4",7110:"a4138606",7120:"d6f82427",7144:"86c72098",7164:"3c8d8ede",7184:"c9ec90d4",7204:"81af1be0",7208:"44d941b2",7222:"ffe2646b",7228:"382982f4",7243:"8fd7c45b",7244:"d16a39fb",7256:"0fac1a2c",7272:"66c0a11c",7280:"b267951c",7312:"ee6f90bf",7320:"d362c5fe",7332:"7594329c",7336:"d4512163",7348:"5e0fc434",7384:"a5774bf9",7412:"7656bfed",7428:"fb13f307",7432:"5602b489",7440:"593f400d",7456:"74f458fe",7492:"542b781f",7516:"77a4fbcd",7540:"d77a4e47",7556:"7f76249c",7604:"1f0dfb0e",7608:"1d3eecd3",7688:"7aac35f5",7720:"d76fc684",7736:"7b6d5cc7",7764:"a0cbcab9",7776:"b3deb321",7792:"24db1b25",7820:"e971379b",7824:"a533f647",7844:"9ead78e2",7868:"859d0b47",7872:"65b0ccd8",7968:"563dbc3a",7988:"51232986",8008:"3d19ea33",8040:"53d02a75",8044:"976591e0",8056:"360399f4",8112:"bba7e25c",8120:"ede04743",8140:"e956a914",8180:"638ee0e5",8212:"4bbc8683",8216:"e475845a",8264:"c177acf1",8268:"ce379bdb",8276:"7b522167",8292:"e839c448",8368:"8d0ee5bd",8376:"0b77a5a6",8388:"5e3effd9",8392:"45075116",8403:"fc02a093",8436:"4a8e4069",8476:"973d031d",8487:"2371cdca",8488:"6ca68d19",8502:"08ab2486",8532:"aa370902",8564:"d38ff788",8592:"5baf8af7",8620:"58792d8f",8632:"daa2beca",8666:"fb70c63e",8756:"1b47a78f",8800:"faa9ad6a",8808:"2c2c7e65",8848:"1e279d76",8879:"e84a6a97",8888:"c0030abc",8980:"91af5075",9e3:"2c4ff459",9008:"1a5bfc05",9020:"6f0032f5",9072:"a0cbb623",9076:"85f07285",9146:"2950aab1",9160:"6785e7f1",9188:"df287e97",9192:"768ca3cc",9220:"c1f30c23",9232:"0e6c6273",9240:"6cf233c1",9248:"1c899cfc",9260:"105deba4",9300:"28a597e3",9344:"a8e564e8",9380:"d5f98963",9384:"0bbcd97d",9404:"01a5ddca",9424:"dc77bd11",9450:"a7708eac",9480:"e8ca5839",9496:"dbedb6d1",9516:"567a7ecb",9540:"338c6b28",9548:"8186f861",9568:"b09195de",9572:"d49697f3",9620:"7d49434f",9624:"b97a436a",9632:"ac178506",9656:"c22080b2",9684:"a3c2ccff",9700:"622a44d4",9720:"0d349797",9724:"d8fef2af",9728:"6cc5f542",9752:"f528e65a",9760:"d5ccd140",9764:"a314a6d6",9772:"344f2223",9784:"876c4e45",9890:"79d9dc82",9936:"e94b3327",9958:"10df8a44",9976:"763b09e8",9980:"02d2fcef",9988:"3e1bcea1",9998:"625892ff"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},b="documentation:",r.l=(e,c,a,d)=>{if(f[e])f[e].push(c);else{var t,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var b=f[e];if(delete f[e],t.parentNode&&t.parentNode.removeChild(t),b&&b.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={10691446:"64",17896441:"6752",19031211:"784",20702474:"4088",31874089:"4304",31910907:"2020",37833312:"9890",54071611:"2813",54828236:"4950",76827885:"9000",84164204:"4536",94009283:"9988",94056865:"9076",d85472ef:"66","986c3199":"68","8c9fccb5":"76","5a824d24":"82","245df310":"88",af8bcf34:"127",ad753fe5:"192",fbb5ccd8:"204","3a8a9a02":"224","31a3eb6f":"240","20bb4ef4":"276",ed111213:"300","2c8be122":"304","77c0bb11":"308","2c32570e":"340",a29806db:"384","53f60aaa":"388",abe60d8e:"392",f3323e34:"400","245b95ea":"464","1675a853":"532",ffafab28:"556",f4ed0a4d:"576","41e9fc02":"592",add102e2:"608","63cffe9f":"640",c6d7e964:"716","9dd4b0ef":"726",c21f0827:"760",b8242e5d:"780","4683e51a":"792",bfdf68d4:"804",e5c09629:"824","5040bcd3":"836",a765b786:"866",db5219b9:"940",a291d650:"984","0c2c09cf":"1028",a55de8d9:"1040","6855260d":"1044",d6d55995:"1144","4ce02702":"1152","4ee994fc":"1156","7c71f28e":"1236","38a9fada":"1260","114e21b0":"1264","2529077f":"1360","9f93909f":"1380","03d23ece":"1424","3c75101b":"1448","3919a682":"1504",fdca9218:"1512",ff4e10c4:"1532",ec989c0b:"1538","321c3473":"1580","1c74e866":"1632","69995e4b":"1640","0bfb57e8":"1724",fcdb2c3c:"1804","1958aa06":"1808","77aa4d93":"1824","10f6dc74":"1860","1ebbf8ad":"1912","023af9ff":"1932",fc7b1962:"1960","0d59295f":"2000","2a5e16ec":"2008","8013ae06":"2030","93d36f00":"2032","53ff84c0":"2068","303f2916":"2134","360898bf":"2140","1d2ace06":"2152",e848ef61:"2164","8b4b94e0":"2168",d3ed5dd0:"2180","1db39f58":"2208","4b4e43c1":"2224","540a99bf":"2240",a8497d64:"2252","6459b84b":"2280",c8555b7b:"2288",dbdbfb09:"2324","149faf5c":"2352",b5a6ea24:"2360","99ff9a84":"2392","02e9e60d":"2492",abba3c1a:"2496","38e3e5e2":"2504","3c8cc128":"2508",f981018f:"2514","02dc5f66":"2520","0fdfb154":"2528",e08582aa:"2540","6ca79f64":"2548","92611ec4":"2556","33939c05":"2560",abd7c6e1:"2620",c4f5d8e4:"2632","99dcebb4":"2692",a193a73b:"2704",c8795c05:"2724","28b502c2":"2728","128066bb":"2760","4504fa21":"2788","66b63dfc":"2804","630dcb4d":"2844",b626b9c9:"2856",c417dca4:"2860","36ca3315":"2900","892cf132":"2904",aaf1cba5:"2908","9c612400":"2956","7b4e6d79":"2968",a10ffd78:"2988","427fd2ae":"3072","15e12a2f":"3088","1c647d6d":"3096","90c91afe":"3104",a7d0887a:"3116","9fcd2aaf":"3128","92ea068e":"3140",ace976d6:"3164","7c884429":"3168",b2c1c78c:"3184","66d590de":"3188","6e7ac859":"3240","6095db2e":"3264",e8f086b4:"3268","60321f1e":"3284","9ec0fefb":"3300","8e61a622":"3330","47a03c7f":"3336","2f4205f9":"3340",effa6628:"3348",df203c0f:"3400","57aa3e64":"3404","06abd5b6":"3408","934030ed":"3436",b7b5f722:"3440","9f7214f0":"3448",e63fc1b2:"3512","1746ec49":"3548",aa3c5b40:"3564","1c2e3411":"3638",d72ff326:"3656","40d100db":"3662",fd1ff340:"3668",dbe067dd:"3676","3dd44646":"3732","45778f4a":"3752","18ee9ead":"3764","0b3ceb4d":"3800","9ba65a7f":"3808","67ad9935":"3872",c02ab877:"3876","42656b86":"3878","82d76014":"3884",b9d99630:"3888",da8b188d:"3936","0a783a8b":"3944","2e6078dc":"3972",f2b76d46:"4006",d8325a2c:"4028",cc5c6d64:"4040","76f67042":"4050",d4124539:"4108","33f68dcb":"4128","7c142331":"4164","9d6a5804":"4184","70b24a51":"4196",f119515c:"4235","2af38141":"4236",a247f8ec:"4272","55960ee5":"4296","2f6f9645":"4300","75d21731":"4376","4d3c06b1":"4412","4a69c104":"4416","5ecea96b":"4424",ef449f35:"4432","7c87e584":"4436","92b2ca3c":"4443","491bc18c":"4464","819f8db2":"4466","3720c009":"4492","332d52b4":"4540",b4ea3a21:"4624",c3ec0a52:"4664","956e9f49":"4700","693f6ee0":"4704","374b0855":"4740","27c465e4":"4772","40185eb5":"4776","3fa6a9b5":"4820",cf3ee67c:"4888",aca4b983:"4891","87d519de":"4901",fe4c7294:"4920","9650c219":"4932","27299a3b":"4936","9cc140f3":"4944",b5a0697e:"4956","9522a145":"4964",a49f1700:"4968",e1c5dcea:"4984",c4cc7857:"4992",e8ac06ec:"5016","1626590c":"5108","05614d2f":"5120",c4023386:"5160","35891e33":"5203",b50f730b:"5222","30644fd9":"5244",b3eefa80:"5248",dae7336e:"5308","4015baac":"5316","2e3626b2":"5328","13f612ac":"5344","8882a989":"5420","826b2a47":"5456",ec13b678:"5464","4a272307":"5488","2457aad7":"5496","2f1df01f":"5544","8fce0176":"5566","7d1f259c":"5572",ef04c7c8:"5596",df6a485b:"5606",f1acbeca:"5620",eb3c6d3c:"5631","881a9bc1":"5660",b221159f:"5674","935f2afb":"5696","2ac485cd":"5728","0d75c27e":"5748",eeb62e5b:"5752","4890df88":"5800","28451e1a":"5816",c2998ab0:"5836","687b8652":"5882","30dc594d":"5892","1f987ab0":"5928","8c90118e":"5976",c51d05bf:"6000","13d06b8c":"6011","5e76b55a":"6020","47355b93":"6092","8e0e9e2b":"6136",bacc2700:"6176","33b191bc":"6180","9cd87160":"6262",b1c5cbf2:"6266","89918c0c":"6284","23c8336c":"6320","7458314f":"6419","53df34ca":"6428","5ba01c12":"6442",de0ecdfa:"6513","1b1d605d":"6560",f187f30c:"6568","3ffc09c4":"6580","18c33990":"6668",cf7e6749:"6692","0e1a79a5":"6720","6127c255":"6724",c16bfc7e:"6764","834e3df2":"6768","8dd1624d":"6796",d055169c:"6808","2a15d269":"6860","5ea00916":"6864",db5d8c25:"6872","4d7c6870":"6876","1647ec57":"6904","28055a57":"6956","5e923e9d":"6964","144669f6":"6976","53fc899c":"6992","31053c79":"7040","450e6318":"7108","738bceb6":"7110","4e202c12":"7120",da105351:"7144",f0ea2bc1:"7164","963bb5ab":"7184",e8e0ef40:"7204",d52025df:"7208","7b0d43d2":"7222",d34bd87a:"7228",fd3f6a3d:"7243","9c985f15":"7244","4bf0522b":"7256","1b07933a":"7272",a1a6e4a2:"7280","959be4b4":"7312",f6807fb4:"7320","98999a3e":"7332","22ecef17":"7336","5ac833b0":"7348",d994236d:"7384","2b026185":"7412","653187f9":"7428","29d4a56b":"7432","0d8578a6":"7440",b65ccb97:"7456","327f2b8d":"7492","4d0c2aca":"7516",a63c68ec:"7540",bd783ed9:"7556","4d96365a":"7604","44a2c628":"7608","3b074962":"7688","8f47f31d":"7720","4610191c":"7736",d5f37c55:"7764","59ee4ecb":"7776",ddec9574:"7792",dae33245:"7820","69bce674":"7824","9bbc65ac":"7844","26084ccc":"7868",f1121274:"7872","3d9aab4f":"7968","28a91100":"7988",c7c4bc80:"8008",fa1390c3:"8040","61086f7d":"8044","9e660ab4":"8056","415f74f4":"8112","96944d2a":"8120","9944c6d1":"8140","3f06413e":"8180","6777c82a":"8212",deb44a68:"8216","14935a59":"8264","27268bf4":"8268",ecd5baba:"8276","8375e767":"8292",e5ac4d56:"8368","8ecb0387":"8376",f32194d4:"8388","4707ca8c":"8392",c3fa5730:"8403",af738211:"8436",a71c172d:"8476","13140ae3":"8487","518c31a4":"8488","62e78712":"8502","018bfed5":"8532",df8fe5f1:"8564","2ddf079f":"8592","525560b7":"8620","55ea0897":"8632","86b21c86":"8666","01255979":"8756","1b728867":"8800",ef35623d:"8808","0688ea12":"8848","7ced6537":"8888","3ce8e004":"8980","9840276f":"9008","92ca3f12":"9020","93746dcf":"9072",f7bd22f2:"9146","1f4f2990":"9160","411cd95c":"9188",db1fb617:"9192","19bbdee1":"9220","7dbacb84":"9232",aa32d378:"9240","7768a617":"9248","9e0bb3f7":"9260","2f3ae6a8":"9300","3b382b4c":"9344","9465b6bc":"9380",fba107f3:"9384",df59c461:"9404","4047e3d8":"9424","044a7006":"9450","0aef6e2f":"9480","1abe76cc":"9496",dbbc6b2c:"9516",a8254ede:"9540",b2aa2b43:"9548","70943eb8":"9568",a8bcf301:"9572",bb0f6255:"9620","329f7802":"9624",cb5f486b:"9632","1be78505":"9656",c1bf43f6:"9684","2aaa4378":"9700",bb32ce55:"9720","58f8f5d8":"9724",bab8e2ef:"9728",a4ce67ea:"9752","1d502ec8":"9760","43ff30f2":"9764","1fa6cfaa":"9784","568e42b9":"9936","136a2cf4":"9958",bddd0f35:"9976","6aabc5ba":"9998"}[e]||e,r.p+r.u(e)},(()=>{var e={296:0,2176:0};r.f.j=(c,a)=>{var f=r.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^2(17|9)6$/.test(c))e[c]=0;else{var b=new Promise(((a,b)=>f=e[c]=[a,b]));a.push(f[2]=b);var d=r.p+r.u(c),t=new Error;r.l(d,(a=>{if(r.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var b=a&&("load"===a.type?"missing":a.type),d=a&&a.target&&a.target.src;t.message="Loading chunk "+c+" failed.\n("+b+": "+d+")",t.name="ChunkLoadError",t.type=b,t.request=d,f[1](t)}}),"chunk-"+c,c)}},r.O.j=c=>0===e[c];var c=(c,a)=>{var f,b,d=a[0],t=a[1],o=a[2],n=0;if(d.some((c=>0!==e[c]))){for(f in t)r.o(t,f)&&(r.m[f]=t[f]);if(o)var i=o(r)}for(c&&c(a);n{"use strict";var e,c,a,f,b,d={},t={};function r(e){var c=t[e];if(void 0!==c)return c.exports;var a=t[e]={exports:{}};return d[e].call(a.exports,a,a.exports,r),a.exports}r.m=d,e=[],r.O=(c,a,f,b)=>{if(!a){var d=1/0;for(i=0;i=b)&&Object.keys(r.O).every((e=>r.O[e](a[o])))?a.splice(o--,1):(t=!1,b0&&e[i-1][2]>b;i--)e[i]=e[i-1];e[i]=[a,f,b]},r.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return r.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var b=Object.create(null);r.r(b);var d={};c=c||[null,a({}),a([]),a(a)];for(var t=2&f&&e;"object"==typeof t&&!~c.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach((c=>d[c]=()=>e[c]));return d.default=()=>e,r.d(b,d),b},r.d=(e,c)=>{for(var a in c)r.o(c,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((c,a)=>(r.f[a](e,c),c)),[])),r.u=e=>"assets/js/"+({64:"10691446",66:"d85472ef",68:"986c3199",76:"8c9fccb5",82:"5a824d24",88:"245df310",127:"af8bcf34",192:"ad753fe5",204:"fbb5ccd8",224:"3a8a9a02",240:"31a3eb6f",276:"20bb4ef4",300:"ed111213",304:"2c8be122",308:"77c0bb11",340:"2c32570e",384:"a29806db",388:"53f60aaa",392:"abe60d8e",400:"f3323e34",464:"245b95ea",532:"1675a853",556:"ffafab28",576:"f4ed0a4d",592:"41e9fc02",608:"add102e2",640:"63cffe9f",716:"c6d7e964",726:"9dd4b0ef",760:"c21f0827",780:"b8242e5d",784:"19031211",792:"4683e51a",804:"bfdf68d4",824:"e5c09629",836:"5040bcd3",866:"a765b786",940:"db5219b9",984:"a291d650",1028:"0c2c09cf",1040:"a55de8d9",1044:"6855260d",1144:"d6d55995",1152:"4ce02702",1156:"4ee994fc",1236:"7c71f28e",1260:"38a9fada",1264:"114e21b0",1360:"2529077f",1380:"9f93909f",1424:"03d23ece",1448:"3c75101b",1504:"3919a682",1512:"fdca9218",1532:"ff4e10c4",1538:"ec989c0b",1580:"321c3473",1632:"1c74e866",1640:"69995e4b",1724:"0bfb57e8",1804:"fcdb2c3c",1808:"1958aa06",1824:"77aa4d93",1860:"10f6dc74",1912:"1ebbf8ad",1932:"023af9ff",1960:"fc7b1962",2e3:"0d59295f",2008:"2a5e16ec",2020:"31910907",2030:"8013ae06",2032:"93d36f00",2068:"53ff84c0",2134:"303f2916",2140:"360898bf",2152:"1d2ace06",2164:"e848ef61",2168:"8b4b94e0",2180:"d3ed5dd0",2208:"1db39f58",2224:"4b4e43c1",2240:"540a99bf",2252:"a8497d64",2280:"6459b84b",2288:"c8555b7b",2324:"dbdbfb09",2352:"149faf5c",2360:"b5a6ea24",2392:"99ff9a84",2492:"02e9e60d",2496:"abba3c1a",2504:"38e3e5e2",2508:"3c8cc128",2514:"f981018f",2520:"02dc5f66",2528:"0fdfb154",2540:"e08582aa",2548:"6ca79f64",2556:"92611ec4",2560:"33939c05",2620:"abd7c6e1",2632:"c4f5d8e4",2692:"99dcebb4",2704:"a193a73b",2724:"c8795c05",2728:"28b502c2",2760:"128066bb",2788:"4504fa21",2804:"66b63dfc",2813:"54071611",2844:"630dcb4d",2856:"b626b9c9",2860:"c417dca4",2900:"36ca3315",2904:"892cf132",2908:"aaf1cba5",2956:"9c612400",2968:"7b4e6d79",2988:"a10ffd78",3072:"427fd2ae",3088:"15e12a2f",3096:"1c647d6d",3104:"90c91afe",3116:"a7d0887a",3128:"9fcd2aaf",3140:"92ea068e",3164:"ace976d6",3168:"7c884429",3184:"b2c1c78c",3188:"66d590de",3240:"6e7ac859",3264:"6095db2e",3268:"e8f086b4",3284:"60321f1e",3300:"9ec0fefb",3330:"8e61a622",3336:"47a03c7f",3340:"2f4205f9",3348:"effa6628",3400:"df203c0f",3404:"57aa3e64",3408:"06abd5b6",3436:"934030ed",3440:"b7b5f722",3448:"9f7214f0",3512:"e63fc1b2",3548:"1746ec49",3564:"aa3c5b40",3638:"1c2e3411",3656:"d72ff326",3662:"40d100db",3668:"fd1ff340",3676:"dbe067dd",3732:"3dd44646",3752:"45778f4a",3764:"18ee9ead",3800:"0b3ceb4d",3808:"9ba65a7f",3872:"67ad9935",3876:"c02ab877",3878:"42656b86",3884:"82d76014",3888:"b9d99630",3936:"da8b188d",3944:"0a783a8b",3972:"2e6078dc",4006:"f2b76d46",4028:"d8325a2c",4040:"cc5c6d64",4050:"76f67042",4088:"20702474",4108:"d4124539",4128:"33f68dcb",4164:"7c142331",4184:"9d6a5804",4196:"70b24a51",4235:"f119515c",4236:"2af38141",4272:"a247f8ec",4296:"55960ee5",4300:"2f6f9645",4304:"31874089",4376:"75d21731",4412:"4d3c06b1",4416:"4a69c104",4424:"5ecea96b",4432:"ef449f35",4436:"7c87e584",4443:"92b2ca3c",4464:"491bc18c",4466:"819f8db2",4492:"3720c009",4536:"84164204",4540:"332d52b4",4624:"b4ea3a21",4664:"c3ec0a52",4700:"956e9f49",4704:"693f6ee0",4740:"374b0855",4772:"27c465e4",4776:"40185eb5",4820:"3fa6a9b5",4888:"cf3ee67c",4891:"aca4b983",4901:"87d519de",4920:"fe4c7294",4932:"9650c219",4936:"27299a3b",4944:"9cc140f3",4950:"54828236",4956:"b5a0697e",4964:"9522a145",4968:"a49f1700",4984:"e1c5dcea",4992:"c4cc7857",5016:"e8ac06ec",5108:"1626590c",5120:"05614d2f",5160:"c4023386",5203:"35891e33",5222:"b50f730b",5244:"30644fd9",5248:"b3eefa80",5308:"dae7336e",5316:"4015baac",5328:"2e3626b2",5344:"13f612ac",5420:"8882a989",5456:"826b2a47",5464:"ec13b678",5488:"4a272307",5496:"2457aad7",5544:"2f1df01f",5566:"8fce0176",5572:"7d1f259c",5596:"ef04c7c8",5606:"df6a485b",5620:"f1acbeca",5631:"eb3c6d3c",5660:"881a9bc1",5674:"b221159f",5696:"935f2afb",5728:"2ac485cd",5748:"0d75c27e",5752:"eeb62e5b",5800:"4890df88",5816:"28451e1a",5836:"c2998ab0",5882:"687b8652",5892:"30dc594d",5928:"1f987ab0",5976:"8c90118e",6e3:"c51d05bf",6011:"13d06b8c",6020:"5e76b55a",6092:"47355b93",6136:"8e0e9e2b",6176:"bacc2700",6180:"33b191bc",6262:"9cd87160",6266:"b1c5cbf2",6284:"89918c0c",6320:"23c8336c",6419:"7458314f",6428:"53df34ca",6442:"5ba01c12",6513:"de0ecdfa",6560:"1b1d605d",6568:"f187f30c",6580:"3ffc09c4",6668:"18c33990",6692:"cf7e6749",6720:"0e1a79a5",6724:"6127c255",6752:"17896441",6764:"c16bfc7e",6768:"834e3df2",6796:"8dd1624d",6808:"d055169c",6860:"2a15d269",6864:"5ea00916",6872:"db5d8c25",6876:"4d7c6870",6904:"1647ec57",6956:"28055a57",6964:"5e923e9d",6976:"144669f6",6992:"53fc899c",7040:"31053c79",7108:"450e6318",7110:"738bceb6",7120:"4e202c12",7144:"da105351",7164:"f0ea2bc1",7184:"963bb5ab",7204:"e8e0ef40",7208:"d52025df",7222:"7b0d43d2",7228:"d34bd87a",7243:"fd3f6a3d",7244:"9c985f15",7256:"4bf0522b",7272:"1b07933a",7280:"a1a6e4a2",7312:"959be4b4",7320:"f6807fb4",7332:"98999a3e",7336:"22ecef17",7348:"5ac833b0",7384:"d994236d",7412:"2b026185",7428:"653187f9",7432:"29d4a56b",7440:"0d8578a6",7456:"b65ccb97",7492:"327f2b8d",7516:"4d0c2aca",7540:"a63c68ec",7556:"bd783ed9",7604:"4d96365a",7608:"44a2c628",7688:"3b074962",7720:"8f47f31d",7736:"4610191c",7764:"d5f37c55",7776:"59ee4ecb",7792:"ddec9574",7820:"dae33245",7824:"69bce674",7844:"9bbc65ac",7868:"26084ccc",7872:"f1121274",7968:"3d9aab4f",7988:"28a91100",8008:"c7c4bc80",8040:"fa1390c3",8044:"61086f7d",8056:"9e660ab4",8112:"415f74f4",8120:"96944d2a",8140:"9944c6d1",8180:"3f06413e",8212:"6777c82a",8216:"deb44a68",8264:"14935a59",8268:"27268bf4",8276:"ecd5baba",8292:"8375e767",8368:"e5ac4d56",8376:"8ecb0387",8388:"f32194d4",8392:"4707ca8c",8403:"c3fa5730",8436:"af738211",8476:"a71c172d",8487:"13140ae3",8488:"518c31a4",8502:"62e78712",8532:"018bfed5",8564:"df8fe5f1",8592:"2ddf079f",8620:"525560b7",8632:"55ea0897",8666:"86b21c86",8756:"01255979",8800:"1b728867",8808:"ef35623d",8848:"0688ea12",8888:"7ced6537",8980:"3ce8e004",9e3:"76827885",9008:"9840276f",9020:"92ca3f12",9072:"93746dcf",9076:"94056865",9146:"f7bd22f2",9160:"1f4f2990",9188:"411cd95c",9192:"db1fb617",9220:"19bbdee1",9232:"7dbacb84",9240:"aa32d378",9248:"7768a617",9260:"9e0bb3f7",9300:"2f3ae6a8",9344:"3b382b4c",9380:"9465b6bc",9384:"fba107f3",9404:"df59c461",9424:"4047e3d8",9450:"044a7006",9480:"0aef6e2f",9496:"1abe76cc",9516:"dbbc6b2c",9540:"a8254ede",9548:"b2aa2b43",9568:"70943eb8",9572:"a8bcf301",9620:"bb0f6255",9624:"329f7802",9632:"cb5f486b",9656:"1be78505",9684:"c1bf43f6",9700:"2aaa4378",9720:"bb32ce55",9724:"58f8f5d8",9728:"bab8e2ef",9752:"a4ce67ea",9760:"1d502ec8",9764:"43ff30f2",9784:"1fa6cfaa",9890:"37833312",9936:"568e42b9",9958:"136a2cf4",9976:"bddd0f35",9988:"94009283",9998:"6aabc5ba"}[e]||e)+"."+{64:"e03d843e",66:"a2a3175d",68:"7c140542",76:"2f3d9000",82:"2a5423d2",88:"f7d4bf9d",127:"1d6a9505",148:"ce04d052",192:"fd6826d6",204:"ad77b4ec",224:"7772fd45",240:"b6868629",276:"b7de9d84",300:"cccd7d35",304:"fbf95519",308:"671143b6",340:"318f27cc",384:"498ba1c0",388:"56f0cb36",392:"35bdac05",400:"4396eb91",464:"aa166608",532:"0381da54",556:"b33d6a55",576:"0d707963",592:"2f1afd17",608:"e5e0fd43",640:"aba14ab8",716:"f5900e0e",726:"f197447c",760:"0e887fd3",780:"d0c6391c",784:"bd623f85",792:"61e31e0f",804:"8b672dfc",824:"fbdc73f1",836:"9cd0536c",866:"a4dbd854",940:"35d55f91",984:"182e8ab7",1028:"0eb1472f",1040:"f29019cb",1044:"ce14a136",1144:"32ab05a6",1152:"9920f714",1156:"0b95df45",1236:"f021ef13",1260:"9afecba6",1264:"bf69cc0e",1360:"a3662a76",1380:"27eb7eb5",1424:"58d6faa5",1448:"0791c839",1504:"230ce4cf",1512:"8a41043d",1532:"dc911aa0",1538:"3bdc0b28",1580:"6c016d82",1632:"208267e7",1640:"837e4187",1676:"b182d0d6",1724:"5792d227",1804:"5480251d",1808:"ab36ddc0",1824:"51db2f08",1848:"f5e87e7f",1860:"5a72fd3f",1912:"643ce78d",1932:"ee07d96e",1960:"1594c6f2",2e3:"699756de",2008:"3aaf1f24",2020:"9c57aed8",2030:"5e749070",2032:"9389e9bf",2068:"781a3f32",2134:"ad02c85e",2140:"942ca312",2152:"872dae6c",2164:"84cb0b3c",2168:"d285c023",2180:"3c1a886a",2208:"816a0c7a",2224:"e351b1e9",2240:"bbb57199",2252:"2abf6129",2280:"257a7b0e",2288:"26026ac3",2324:"e10dcb78",2352:"96897cd4",2360:"a30bb4e7",2392:"4ca1309f",2492:"94dc7312",2496:"bd1c8254",2504:"764c53b0",2508:"8dcc6b4b",2514:"cdb6dc50",2520:"a9450ec4",2528:"e06e4790",2540:"6c562438",2548:"237fe638",2556:"cbdd35f2",2560:"f2391da7",2592:"f79bfc25",2620:"a49e91d2",2632:"9e99648f",2692:"302f60c4",2704:"29b855cd",2724:"4c24023b",2728:"1130f732",2760:"9a294ff5",2788:"e4f7a7d3",2804:"125db56d",2813:"fdad7756",2844:"5b53eb95",2856:"1fe563d7",2860:"0540c3dc",2900:"314f4d1e",2904:"414c2fba",2908:"9899e33d",2956:"bccbe11f",2968:"0990cec0",2988:"706b078c",3072:"6a03a665",3088:"dd7086b6",3096:"935fdd63",3104:"b6b9a45d",3116:"abb8d70b",3128:"158d4028",3140:"860a029e",3164:"2f79d741",3168:"0909b306",3184:"f7aedccd",3188:"93f0783d",3240:"414ed64e",3264:"b392a7b5",3268:"b1f67458",3284:"3c85b27d",3300:"95181847",3330:"79061c76",3336:"821a5c56",3340:"cebf54b5",3348:"6be50f37",3400:"a1d2b02a",3404:"a4bdb4d0",3408:"8cf76dba",3436:"f2ea5a74",3440:"a897bd73",3448:"cb396bc4",3512:"34d0593e",3548:"d06b6769",3564:"fed6dfcf",3638:"a1e97033",3656:"f11edc6f",3662:"cb95af95",3668:"03ec2f2e",3676:"e7848cf0",3732:"53479fd9",3752:"d9ff558f",3764:"b9026d53",3800:"dfb95537",3808:"e6112df6",3872:"08497ea6",3876:"bd68bdeb",3878:"3312c963",3884:"1ba336e9",3888:"1bf95994",3936:"0951d6c3",3944:"be235f34",3972:"895e18e8",4006:"b60f28ef",4028:"6c371536",4040:"35b13b21",4050:"2fcd8697",4088:"68f7594b",4108:"d0e7efcb",4128:"425cd3d1",4164:"7076e2de",4184:"cf4b1a8b",4196:"d6a9cb1c",4235:"8aa4f1f8",4236:"039d38db",4272:"ff3c3554",4296:"f8f72c84",4300:"5abb1c55",4304:"cfdd4711",4376:"f295987b",4412:"8c636985",4416:"03c614df",4424:"49d57c0a",4432:"9fb2e433",4436:"2708aaf1",4443:"41ed0f79",4464:"8453a132",4466:"60d25c4d",4492:"057ff55e",4536:"6a531817",4540:"4e4cb182",4624:"cf2cd490",4664:"55f90633",4700:"166464d2",4704:"e1fe6f49",4740:"4950ec81",4772:"4f6c8e90",4776:"b4aa1eca",4820:"b5a6b9f9",4888:"82ae85c2",4891:"a3b3efa7",4901:"ac12d733",4920:"8beb6b75",4932:"813cbebd",4936:"93f5a6c6",4944:"88fd156f",4950:"5ae5254a",4956:"3aad476d",4964:"357238f0",4968:"6b974736",4984:"24dc9b4d",4992:"a9f4c793",5016:"5700b32f",5108:"8f5f9a80",5120:"ee6b3e70",5160:"9abfb881",5203:"37464699",5222:"b4b2f3ae",5244:"5758235c",5248:"b3c460a0",5308:"79fa30cd",5316:"6b98909d",5328:"5a9e1a76",5344:"d645b040",5420:"bc1f28ba",5456:"615fb125",5464:"47ac9494",5488:"b13f013e",5496:"3865d741",5544:"8720b9ef",5566:"366af879",5572:"3cd92a5f",5596:"1b2409bd",5606:"ddad1a20",5620:"950a6f83",5631:"59487b1d",5660:"c7c44aae",5674:"ea702f9c",5696:"f1eb8702",5728:"60b84ce1",5748:"846988bb",5752:"e08db8e5",5800:"1c683c72",5816:"ee085898",5836:"0664c43a",5882:"fd7d220b",5892:"d937092d",5928:"236cc305",5976:"10833c6f",6e3:"9973b90b",6011:"deea3b76",6020:"38044a2a",6092:"470a4b8e",6136:"72d78faa",6176:"3514dd94",6180:"7ead1419",6262:"d0f1292f",6266:"c89a7d60",6284:"5d47c466",6320:"41ddab3c",6419:"31fba5b3",6428:"f7d3ad96",6442:"fd5b9d86",6513:"ef782f41",6560:"4a6c51d5",6568:"400f22f2",6579:"5388cc08",6580:"c2453e03",6668:"4ddc7406",6692:"901f95da",6720:"8a9f16a1",6724:"06724ddb",6752:"dd61cb89",6764:"a0617972",6768:"8bb4739b",6796:"fbf17ae0",6808:"400e7bda",6860:"7a363877",6864:"102e1842",6872:"7f1d9688",6876:"7cc387d7",6904:"77f851e7",6956:"5a1f67d4",6964:"0cecdb26",6976:"226d4280",6992:"a530b4b0",7040:"d43d962b",7108:"ca99a9e4",7110:"a4138606",7120:"d6f82427",7144:"86c72098",7164:"3c8d8ede",7184:"c9ec90d4",7204:"81af1be0",7208:"44d941b2",7222:"ffe2646b",7228:"382982f4",7243:"8fd7c45b",7244:"d16a39fb",7256:"0fac1a2c",7272:"66c0a11c",7280:"b267951c",7312:"ee6f90bf",7320:"d362c5fe",7332:"7594329c",7336:"d4512163",7348:"5e0fc434",7384:"a5774bf9",7412:"7656bfed",7428:"fb13f307",7432:"5602b489",7440:"593f400d",7456:"74f458fe",7492:"542b781f",7516:"77a4fbcd",7540:"d77a4e47",7556:"7f76249c",7604:"1f0dfb0e",7608:"1d3eecd3",7688:"7aac35f5",7720:"d76fc684",7736:"7b6d5cc7",7764:"a0cbcab9",7776:"b3deb321",7792:"24db1b25",7820:"e971379b",7824:"a533f647",7844:"9ead78e2",7868:"859d0b47",7872:"65b0ccd8",7968:"563dbc3a",7988:"51232986",8008:"3d19ea33",8040:"53d02a75",8044:"976591e0",8056:"360399f4",8112:"bba7e25c",8120:"ede04743",8140:"e956a914",8180:"638ee0e5",8212:"4bbc8683",8216:"e475845a",8264:"c177acf1",8268:"ce379bdb",8276:"7b522167",8292:"e839c448",8368:"8d0ee5bd",8376:"0b77a5a6",8388:"5e3effd9",8392:"45075116",8403:"fc02a093",8436:"4a8e4069",8476:"973d031d",8487:"2371cdca",8488:"6ca68d19",8502:"08ab2486",8532:"aa370902",8564:"d38ff788",8592:"5baf8af7",8620:"58792d8f",8632:"daa2beca",8666:"fb70c63e",8756:"1b47a78f",8800:"faa9ad6a",8808:"2c2c7e65",8848:"1e279d76",8879:"e84a6a97",8888:"c0030abc",8980:"91af5075",9e3:"2c4ff459",9008:"1a5bfc05",9020:"6f0032f5",9072:"a0cbb623",9076:"85f07285",9146:"2950aab1",9160:"6785e7f1",9188:"df287e97",9192:"768ca3cc",9220:"c1f30c23",9232:"0e6c6273",9240:"6cf233c1",9248:"1c899cfc",9260:"105deba4",9300:"28a597e3",9344:"a8e564e8",9380:"d5f98963",9384:"0bbcd97d",9404:"01a5ddca",9424:"dc77bd11",9450:"a7708eac",9480:"e8ca5839",9496:"dbedb6d1",9516:"567a7ecb",9540:"338c6b28",9548:"8186f861",9568:"b09195de",9572:"d49697f3",9620:"7d49434f",9624:"b97a436a",9632:"ac178506",9656:"c22080b2",9684:"a3c2ccff",9700:"622a44d4",9720:"0d349797",9724:"d8fef2af",9728:"6cc5f542",9752:"f528e65a",9760:"d5ccd140",9764:"a314a6d6",9772:"344f2223",9784:"876c4e45",9890:"79d9dc82",9936:"e94b3327",9958:"10df8a44",9976:"763b09e8",9980:"02d2fcef",9988:"3e1bcea1",9998:"625892ff"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),f={},b="documentation:",r.l=(e,c,a,d)=>{if(f[e])f[e].push(c);else{var t,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var b=f[e];if(delete f[e],t.parentNode&&t.parentNode.removeChild(t),b&&b.forEach((e=>e(a))),c)return c(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={10691446:"64",17896441:"6752",19031211:"784",20702474:"4088",31874089:"4304",31910907:"2020",37833312:"9890",54071611:"2813",54828236:"4950",76827885:"9000",84164204:"4536",94009283:"9988",94056865:"9076",d85472ef:"66","986c3199":"68","8c9fccb5":"76","5a824d24":"82","245df310":"88",af8bcf34:"127",ad753fe5:"192",fbb5ccd8:"204","3a8a9a02":"224","31a3eb6f":"240","20bb4ef4":"276",ed111213:"300","2c8be122":"304","77c0bb11":"308","2c32570e":"340",a29806db:"384","53f60aaa":"388",abe60d8e:"392",f3323e34:"400","245b95ea":"464","1675a853":"532",ffafab28:"556",f4ed0a4d:"576","41e9fc02":"592",add102e2:"608","63cffe9f":"640",c6d7e964:"716","9dd4b0ef":"726",c21f0827:"760",b8242e5d:"780","4683e51a":"792",bfdf68d4:"804",e5c09629:"824","5040bcd3":"836",a765b786:"866",db5219b9:"940",a291d650:"984","0c2c09cf":"1028",a55de8d9:"1040","6855260d":"1044",d6d55995:"1144","4ce02702":"1152","4ee994fc":"1156","7c71f28e":"1236","38a9fada":"1260","114e21b0":"1264","2529077f":"1360","9f93909f":"1380","03d23ece":"1424","3c75101b":"1448","3919a682":"1504",fdca9218:"1512",ff4e10c4:"1532",ec989c0b:"1538","321c3473":"1580","1c74e866":"1632","69995e4b":"1640","0bfb57e8":"1724",fcdb2c3c:"1804","1958aa06":"1808","77aa4d93":"1824","10f6dc74":"1860","1ebbf8ad":"1912","023af9ff":"1932",fc7b1962:"1960","0d59295f":"2000","2a5e16ec":"2008","8013ae06":"2030","93d36f00":"2032","53ff84c0":"2068","303f2916":"2134","360898bf":"2140","1d2ace06":"2152",e848ef61:"2164","8b4b94e0":"2168",d3ed5dd0:"2180","1db39f58":"2208","4b4e43c1":"2224","540a99bf":"2240",a8497d64:"2252","6459b84b":"2280",c8555b7b:"2288",dbdbfb09:"2324","149faf5c":"2352",b5a6ea24:"2360","99ff9a84":"2392","02e9e60d":"2492",abba3c1a:"2496","38e3e5e2":"2504","3c8cc128":"2508",f981018f:"2514","02dc5f66":"2520","0fdfb154":"2528",e08582aa:"2540","6ca79f64":"2548","92611ec4":"2556","33939c05":"2560",abd7c6e1:"2620",c4f5d8e4:"2632","99dcebb4":"2692",a193a73b:"2704",c8795c05:"2724","28b502c2":"2728","128066bb":"2760","4504fa21":"2788","66b63dfc":"2804","630dcb4d":"2844",b626b9c9:"2856",c417dca4:"2860","36ca3315":"2900","892cf132":"2904",aaf1cba5:"2908","9c612400":"2956","7b4e6d79":"2968",a10ffd78:"2988","427fd2ae":"3072","15e12a2f":"3088","1c647d6d":"3096","90c91afe":"3104",a7d0887a:"3116","9fcd2aaf":"3128","92ea068e":"3140",ace976d6:"3164","7c884429":"3168",b2c1c78c:"3184","66d590de":"3188","6e7ac859":"3240","6095db2e":"3264",e8f086b4:"3268","60321f1e":"3284","9ec0fefb":"3300","8e61a622":"3330","47a03c7f":"3336","2f4205f9":"3340",effa6628:"3348",df203c0f:"3400","57aa3e64":"3404","06abd5b6":"3408","934030ed":"3436",b7b5f722:"3440","9f7214f0":"3448",e63fc1b2:"3512","1746ec49":"3548",aa3c5b40:"3564","1c2e3411":"3638",d72ff326:"3656","40d100db":"3662",fd1ff340:"3668",dbe067dd:"3676","3dd44646":"3732","45778f4a":"3752","18ee9ead":"3764","0b3ceb4d":"3800","9ba65a7f":"3808","67ad9935":"3872",c02ab877:"3876","42656b86":"3878","82d76014":"3884",b9d99630:"3888",da8b188d:"3936","0a783a8b":"3944","2e6078dc":"3972",f2b76d46:"4006",d8325a2c:"4028",cc5c6d64:"4040","76f67042":"4050",d4124539:"4108","33f68dcb":"4128","7c142331":"4164","9d6a5804":"4184","70b24a51":"4196",f119515c:"4235","2af38141":"4236",a247f8ec:"4272","55960ee5":"4296","2f6f9645":"4300","75d21731":"4376","4d3c06b1":"4412","4a69c104":"4416","5ecea96b":"4424",ef449f35:"4432","7c87e584":"4436","92b2ca3c":"4443","491bc18c":"4464","819f8db2":"4466","3720c009":"4492","332d52b4":"4540",b4ea3a21:"4624",c3ec0a52:"4664","956e9f49":"4700","693f6ee0":"4704","374b0855":"4740","27c465e4":"4772","40185eb5":"4776","3fa6a9b5":"4820",cf3ee67c:"4888",aca4b983:"4891","87d519de":"4901",fe4c7294:"4920","9650c219":"4932","27299a3b":"4936","9cc140f3":"4944",b5a0697e:"4956","9522a145":"4964",a49f1700:"4968",e1c5dcea:"4984",c4cc7857:"4992",e8ac06ec:"5016","1626590c":"5108","05614d2f":"5120",c4023386:"5160","35891e33":"5203",b50f730b:"5222","30644fd9":"5244",b3eefa80:"5248",dae7336e:"5308","4015baac":"5316","2e3626b2":"5328","13f612ac":"5344","8882a989":"5420","826b2a47":"5456",ec13b678:"5464","4a272307":"5488","2457aad7":"5496","2f1df01f":"5544","8fce0176":"5566","7d1f259c":"5572",ef04c7c8:"5596",df6a485b:"5606",f1acbeca:"5620",eb3c6d3c:"5631","881a9bc1":"5660",b221159f:"5674","935f2afb":"5696","2ac485cd":"5728","0d75c27e":"5748",eeb62e5b:"5752","4890df88":"5800","28451e1a":"5816",c2998ab0:"5836","687b8652":"5882","30dc594d":"5892","1f987ab0":"5928","8c90118e":"5976",c51d05bf:"6000","13d06b8c":"6011","5e76b55a":"6020","47355b93":"6092","8e0e9e2b":"6136",bacc2700:"6176","33b191bc":"6180","9cd87160":"6262",b1c5cbf2:"6266","89918c0c":"6284","23c8336c":"6320","7458314f":"6419","53df34ca":"6428","5ba01c12":"6442",de0ecdfa:"6513","1b1d605d":"6560",f187f30c:"6568","3ffc09c4":"6580","18c33990":"6668",cf7e6749:"6692","0e1a79a5":"6720","6127c255":"6724",c16bfc7e:"6764","834e3df2":"6768","8dd1624d":"6796",d055169c:"6808","2a15d269":"6860","5ea00916":"6864",db5d8c25:"6872","4d7c6870":"6876","1647ec57":"6904","28055a57":"6956","5e923e9d":"6964","144669f6":"6976","53fc899c":"6992","31053c79":"7040","450e6318":"7108","738bceb6":"7110","4e202c12":"7120",da105351:"7144",f0ea2bc1:"7164","963bb5ab":"7184",e8e0ef40:"7204",d52025df:"7208","7b0d43d2":"7222",d34bd87a:"7228",fd3f6a3d:"7243","9c985f15":"7244","4bf0522b":"7256","1b07933a":"7272",a1a6e4a2:"7280","959be4b4":"7312",f6807fb4:"7320","98999a3e":"7332","22ecef17":"7336","5ac833b0":"7348",d994236d:"7384","2b026185":"7412","653187f9":"7428","29d4a56b":"7432","0d8578a6":"7440",b65ccb97:"7456","327f2b8d":"7492","4d0c2aca":"7516",a63c68ec:"7540",bd783ed9:"7556","4d96365a":"7604","44a2c628":"7608","3b074962":"7688","8f47f31d":"7720","4610191c":"7736",d5f37c55:"7764","59ee4ecb":"7776",ddec9574:"7792",dae33245:"7820","69bce674":"7824","9bbc65ac":"7844","26084ccc":"7868",f1121274:"7872","3d9aab4f":"7968","28a91100":"7988",c7c4bc80:"8008",fa1390c3:"8040","61086f7d":"8044","9e660ab4":"8056","415f74f4":"8112","96944d2a":"8120","9944c6d1":"8140","3f06413e":"8180","6777c82a":"8212",deb44a68:"8216","14935a59":"8264","27268bf4":"8268",ecd5baba:"8276","8375e767":"8292",e5ac4d56:"8368","8ecb0387":"8376",f32194d4:"8388","4707ca8c":"8392",c3fa5730:"8403",af738211:"8436",a71c172d:"8476","13140ae3":"8487","518c31a4":"8488","62e78712":"8502","018bfed5":"8532",df8fe5f1:"8564","2ddf079f":"8592","525560b7":"8620","55ea0897":"8632","86b21c86":"8666","01255979":"8756","1b728867":"8800",ef35623d:"8808","0688ea12":"8848","7ced6537":"8888","3ce8e004":"8980","9840276f":"9008","92ca3f12":"9020","93746dcf":"9072",f7bd22f2:"9146","1f4f2990":"9160","411cd95c":"9188",db1fb617:"9192","19bbdee1":"9220","7dbacb84":"9232",aa32d378:"9240","7768a617":"9248","9e0bb3f7":"9260","2f3ae6a8":"9300","3b382b4c":"9344","9465b6bc":"9380",fba107f3:"9384",df59c461:"9404","4047e3d8":"9424","044a7006":"9450","0aef6e2f":"9480","1abe76cc":"9496",dbbc6b2c:"9516",a8254ede:"9540",b2aa2b43:"9548","70943eb8":"9568",a8bcf301:"9572",bb0f6255:"9620","329f7802":"9624",cb5f486b:"9632","1be78505":"9656",c1bf43f6:"9684","2aaa4378":"9700",bb32ce55:"9720","58f8f5d8":"9724",bab8e2ef:"9728",a4ce67ea:"9752","1d502ec8":"9760","43ff30f2":"9764","1fa6cfaa":"9784","568e42b9":"9936","136a2cf4":"9958",bddd0f35:"9976","6aabc5ba":"9998"}[e]||e,r.p+r.u(e)},(()=>{var e={296:0,2176:0};r.f.j=(c,a)=>{var f=r.o(e,c)?e[c]:void 0;if(0!==f)if(f)a.push(f[2]);else if(/^2(17|9)6$/.test(c))e[c]=0;else{var b=new Promise(((a,b)=>f=e[c]=[a,b]));a.push(f[2]=b);var d=r.p+r.u(c),t=new Error;r.l(d,(a=>{if(r.o(e,c)&&(0!==(f=e[c])&&(e[c]=void 0),f)){var b=a&&("load"===a.type?"missing":a.type),d=a&&a.target&&a.target.src;t.message="Loading chunk "+c+" failed.\n("+b+": "+d+")",t.name="ChunkLoadError",t.type=b,t.request=d,f[1](t)}}),"chunk-"+c,c)}},r.O.j=c=>0===e[c];var c=(c,a)=>{var f,b,d=a[0],t=a[1],o=a[2],n=0;if(d.some((c=>0!==e[c]))){for(f in t)r.o(t,f)&&(r.m[f]=t[f]);if(o)var i=o(r)}for(c&&c(a);n Debugger | Apiato - + @@ -12,7 +12,7 @@
Version: 10.x

Debugger

Apiato provides a simple and easy way to monitor and log all the HTTP requests coming to your application.

The request monitor can be very useful when testing and debugging your frontend Apps which consume your API. Especially when the frontend apps (Mobile, Web,...) are built by other developers who are far from you.

The requests monitoring is provided via theRequestsMonitorMiddleware middleware.

Installation

composer require apiato/debugger-container
tip

This container is installed by default with an Apiato fresh installation.

Enable Requests Logging

Set REQUESTS_DEBUG and APP_DEBUG to true in .env file .

Usage

Log will be written to storage/logs/debugger.log

Debugger Customization

Instructions

This container works out of the box perfectly but if you want to change its configs or modify the codes you MUST follow these steps:

1- Copy the container from Vendor to AppSection (or any of your custom sections) of your project
2- Fix the namespaces
3- Remove apiato/debugger-container dependency from project root composer.json

Change the Default Log File

By default, everything is logged in the debugger.log file, to change the default file go to Debugger/Configs/debugger.php config file and set the file name:

/*

|--------------------------------------------------------------------------
| Log File
|--------------------------------------------------------------------------
|
| What to name the log file in the `storage/log` path.
|
*/

'log_file' => 'debugger.log',

Run in Testing Environments

Request monitoring will not run in testing environments, to enable it you need to manually edit the Middleware.

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/documentation/index.html b/docs/10.x/additional-features/apiato-containers/documentation/index.html index 0dc6838a2..8023f3b4d 100644 --- a/docs/10.x/additional-features/apiato-containers/documentation/index.html +++ b/docs/10.x/additional-features/apiato-containers/documentation/index.html @@ -4,7 +4,7 @@ Documentation | Apiato - + @@ -17,7 +17,7 @@ 3- Remove apiato/documentation-generator-container dependency from project root composer.json
4- Update section_name & html_files in container configs
5- Update apidoc.json files in ApiDocJs/private & public folders and fix the filename

{
"header": {
"filename": "Containers/NEW_SECTION_NAME/Documentation/UI/WEB/Views/documentation/header.md"
}
}

Edit Default Generated Values in Templates

Apiato by defaults generates 2 API documentations, each one has its own apidoc.json file. Both can be modified from the Documentation Containers in Containers/Vendor/Documentation/ApiDocJs/

Change the Documentations URL's

Edit the config file of the Documentation Container Containers/Vendor/Documentation/Configs/vendor-documentation.php

Edit the Documentation Header

The header is usually the Overview of your API. It contains Info about authenticating users, making requests, responses, potential errors, rate limiting, pagination, query parameters and anything you want.

All this information is written in app/Containers/Vendor/Documentation/ApiDocJs/shared/header.template.md file, and the same file is used as header for both private and public documentations.

To edit the content just open the markdown file in any markdown editor and edit it.

You will notice some variables like {{rate-limit}} and {{token-expires}}. Those are replaced when running apiato:apidoc with real values from your application configuration files.

Feel free to extend them to include more info about your API from the app/Containers/Vendor/Documentation/Tasks/RenderTemplatesTask.php class.

Localization for Documentation Header

Default, the documentation title is in English en localization.

See which locales are supported by going in app/Containers/Vendor/Documentation/ApiDocJs/shared/

There will be some header.template.{locale}.md files in the folder.

You can change the language by adding APIDOC_LOCALE=ru to the .env file.

If you didn't find a file with your locale, you can create it. You need to modify its source code and create new file like header.template.cn.md

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/localization/index.html b/docs/10.x/additional-features/apiato-containers/localization/index.html index c157fd802..f5e5845ec 100644 --- a/docs/10.x/additional-features/apiato-containers/localization/index.html +++ b/docs/10.x/additional-features/apiato-containers/localization/index.html @@ -4,7 +4,7 @@ Localization | Apiato - + @@ -38,7 +38,7 @@ language in this specific language (e.g., locale_name => Deutsch). Furthermore, the language name is outputted in the applications default name (e.g., configured in app.locale). This would result in default_name => German.

The same applies to the regions that are defined (e.g., de-DE). Consequently, this results in locale_name => Deutschland and default_name = Germany.

Tests

To change the default language in your tests requests. You can set the env language in the phpunit.xml file.

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/overview/index.html b/docs/10.x/additional-features/apiato-containers/overview/index.html index ca573520a..057bb1d22 100644 --- a/docs/10.x/additional-features/apiato-containers/overview/index.html +++ b/docs/10.x/additional-features/apiato-containers/overview/index.html @@ -4,14 +4,14 @@ Overview | Apiato - +
Version: 10.x

Overview

Apiato ships with a few pre-defined and pre-configured containers. Some of these containers (e.g. Documentation & Debugger) are installed by default with a fresh Apiato project.

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/payments/index.html b/docs/10.x/additional-features/apiato-containers/payments/index.html index fdad6d8b2..174588a70 100644 --- a/docs/10.x/additional-features/apiato-containers/payments/index.html +++ b/docs/10.x/additional-features/apiato-containers/payments/index.html @@ -4,7 +4,7 @@ Payments | Apiato - + @@ -38,7 +38,7 @@ need to create your own ChargeWithFooTask. This class, however, needs to implement the PaymentChargerInterface distributed via the Payment container. This interface, in turn, requires you to implement the charge() method.

This method needs to connect to the FooService, create the payment and return a PaymentTransaction model.

Finally, you need to register the new service. This can be done in the Payment\Configs\vendor-payment.php file. For the vendor-payment.gateways key, add the new entry for your Foo Payment Gateway. This may look like this:

    // ...
'foo' => [
'container' => 'Foo',
'charge_task' => \App\Containers\Foo\Tasks\ChargeWithFooTask::class,
],
// ...

Basically, this entry points to the charger_task that handles, how to charge a User with the specific Payment Gateway.

That's all!

Mocking the real payment call for Testing

// mock the ChargeWithStripeService external API call
$this->mockIt(ChargeWithStripeService::class)->shouldReceive('charge')->andReturn([
'payment_method' => 'stripe',
'description' => $payId
]);

// mock the ChargeWithPaypalService external API call
$this->mockIt(ChargeWithPaypalService::class)->shouldReceive('charge')->andReturn([
'payment_method' => 'paypal',
'description' => $payId
]);

Checkout the Tests Helpers page for more information about Testing.

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/settings/index.html b/docs/10.x/additional-features/apiato-containers/settings/index.html index 08e82473f..20f3ef431 100644 --- a/docs/10.x/additional-features/apiato-containers/settings/index.html +++ b/docs/10.x/additional-features/apiato-containers/settings/index.html @@ -4,7 +4,7 @@ Settings | Apiato - + @@ -12,7 +12,7 @@
Version: 10.x

Settings

Installation

composer require apiato/settings-container

Now run php artisan migrate

Seed the default settings

Instructions

This container works out of the box perfectly but if you want to change its configs or modify the codes you MUST follow these steps:

1- Copy the container from Vendor to AppSection (or any of your custom sections) of your project
2- Fix the namespaces
3- Remove apiato/settings-container dependency from project root composer.json

Seed default settings in app/Containers/YourSection/Settings/Database/Seeders/DefaultSystemSettingsSeeder.php

Read settings

$value = $this->findSettingsByKeyTask->run('whateverSettingsName');

You can search for settings by Key as in the example above, or create a class for each setting as follows:

$value = $this->findWhateverSettingsTask->run();
- + \ No newline at end of file diff --git a/docs/10.x/additional-features/apiato-containers/social-authentication/index.html b/docs/10.x/additional-features/apiato-containers/social-authentication/index.html index db6106e38..878b19c79 100644 --- a/docs/10.x/additional-features/apiato-containers/social-authentication/index.html +++ b/docs/10.x/additional-features/apiato-containers/social-authentication/index.html @@ -4,7 +4,7 @@ Social Authentication | Apiato - + @@ -21,7 +21,7 @@ 2- Fix the namespaces
3- Remove apiato/social-auth-container dependency from project root composer.json

1) Pick an Auth Provider from the supported providers by Socialite.
2) Go to app/Containers/YourSection/Socialauth/Tasks/FindUserSocialProfileTask.php and support your provider.

- + \ No newline at end of file diff --git a/docs/10.x/additional-features/community-containers/overview/index.html b/docs/10.x/additional-features/community-containers/overview/index.html index 596384a23..f67a4a203 100644 --- a/docs/10.x/additional-features/community-containers/overview/index.html +++ b/docs/10.x/additional-features/community-containers/overview/index.html @@ -4,7 +4,7 @@ Overview | Apiato - + @@ -13,7 +13,7 @@ features in form of a respective container using Container Installer. Although these containers may work perfectly out of the box, they only serve as some kind of "blueprint" in order to get you kickstart your own application. These containers may not contain "production-ready code". Please revise them carefully.

note

These containers are developed by Apiato community and provided as is.

Feel free to add your containers to this list.

How to contribute

  1. Add a good documentation to your container.
  2. Make a PR and add your repository

Community Containers

NameDescriptionLink
ActivityLogLog Activities of Users and ModelsGitHub
NoteA Container handling Notes (e.g., ToDo Lists) for Eloquent Models.GitHub
MFAA Container to manage 2 Factor Authentication works with any Authenticator app.GitHub
SanctumImplementation of Laravel Sanctum in Apiato.GitHub
- + \ No newline at end of file diff --git a/docs/10.x/contribution-guide/index.html b/docs/10.x/contribution-guide/index.html index 1045b3033..efd546f68 100644 --- a/docs/10.x/contribution-guide/index.html +++ b/docs/10.x/contribution-guide/index.html @@ -4,7 +4,7 @@ Contribution Guide | Apiato - + @@ -42,7 +42,7 @@ 3 - Make sure you write a complete Changelog, in the release description. 4 - Change the default branch on GitHub to that new branch. 5 - If you updated the documentation, and you should! then visit the documentation repository and merge the PR into master.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/api-versioning/index.html b/docs/10.x/core-features/api-versioning/index.html index 252ae04dd..b6a481ddd 100644 --- a/docs/10.x/core-features/api-versioning/index.html +++ b/docs/10.x/core-features/api-versioning/index.html @@ -4,13 +4,13 @@ API Versioning | Apiato - +
Version: 10.x

API Versioning

Since Laravel does not support API versioning, Apiato provide a very easy way to implement versioning for your API.

How it works

Create:

When creating a new API endpoint, specify the version number in the route file name following this naming format {endpoint-name}.{version-number}.{documentation-name}.php.

Example:

  • MakeOrder.v1.public.php
  • MakeOrder.v2.public.php
  • ListOrders.v1.private.php

Use:

Automatically the endpoint inside that route file will be accessible by adding the version number to the URL.

Example:

  • http://api.apiato.test/v1/register
  • http://api.apiato.test/v1/orders
  • http://api.apiato.test/v2/stores/123

Version the API in header instead of URL

First remove the URL version prefix:

  1. Edit app/Ship/Configs/apiato.php, set prefix to 'enable_version_prefix' => 'false',.
  2. Implement the Header versioning anyway you prefer. (this is not implemented in Apiato yet. Consider a contribution).
- + \ No newline at end of file diff --git a/docs/10.x/core-features/authentication/index.html b/docs/10.x/core-features/authentication/index.html index 7d093334b..4bf30de83 100644 --- a/docs/10.x/core-features/authentication/index.html +++ b/docs/10.x/core-features/authentication/index.html @@ -4,7 +4,7 @@ Authentication | Apiato - + @@ -58,7 +58,7 @@ It will email you a link and when you make a request to that link it will call the /password-reset endpoint.

Note: For security reason, make sure the reset password URL is set in app/Containers/AppSection/User/Configs/appSection-user.php and given to the client App to be sent as parameter when calling the /password-forgot.

Note: You must set up the email to get this function to work, however for testing purposes set the MAIL_DRIVER=log in your .env file in order to the see the email content in the log file storage/logs/laravel.log.

Social Authentication

For Social Authentication visit the Social Authentication page.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/authorization/index.html b/docs/10.x/core-features/authorization/index.html index d4da6c616..57dfd8723 100644 --- a/docs/10.x/core-features/authorization/index.html +++ b/docs/10.x/core-features/authorization/index.html @@ -4,13 +4,13 @@ Authorization | Apiato - +
Version: 10.x

Authorization

Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

Behind the scenes apiato is using the Laravel's authorization functionality that was introduced in version 5.1.11 with the helper package laravel-permission. So you can always refer to the correspond documentation for more information.

How it works

Authorization in apiato is very simple and easy.

1) Create some Roles and permissions. By default, an admin role and some permissions are provided by Apiato. You can find the code in app/Containers/AppSection/Authorization/Data/Seeders/* directory.

2) Attach some permissions to the roles.

3) Now start creating users (or use existing users), to assign them to the new created Roles.

4) Finally, you need to protect your endpoints by Permissions (or/and Roles). The right place to do that is the Requests class.

Example protecting the (delete user) endpoint with delete-users permission:

class DeleteUserRequest extends Request
{
protected array $access = [
'permissions' => 'delete-users',
'roles' => '',
];

public function authorize(): bool
{
return $this->check([
'hasAccess',
]);
}
}

For detailed explanation of this example, please visit the Requests Page.

Responses

Authorization failed JSON response:

{
"message": "This action is unauthorized."
}

Assign Roles & Permission to the Testing User

You will need to set $access property in your test class, check out the Tests Helpers page for more details.

Seeding some users (Admins)

By default, Apiato comes with a Super Admin.

This Super Admin Credentials are:

This Admin seeded by app/Containers/Authorization/Data/Seeders/AuthorizationDefaultUsersSeeder_3.php.

The Default Super User, has a default role admin.

The admin default role has no permissions given to it.

To give permissions to the admin role (or any other role), you can use the dedicated endpoints (from your custom Admin Interface) or use this command php artisan apiato:permissions:toRole admin to give it all the permissions in the system.

Checkout each container Seeders directory app/Containers/AppSection/{container-name}/Data/Seeders/, to edit the default Users, Roles and Permissions.

Roles & Permissions guards

By default, Apiato uses a single guard called web for all it's roles and permissions, you can add/edit this behavior and support multiple guards at any time. Refer to the laravel-permission package for more details.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/code-generator/index.html b/docs/10.x/core-features/code-generator/index.html index b77bf0cfd..a50e0347b 100644 --- a/docs/10.x/core-features/code-generator/index.html +++ b/docs/10.x/core-features/code-generator/index.html @@ -4,7 +4,7 @@ Code Generator | Apiato - + @@ -17,7 +17,7 @@ the file and the folder structure needs to be the same as in vendor/apiato/core/Generator/Stubs.

Say, if you like to change the action -> create.stub, simply copy the file to app/Ship/Generators/CustomStubs/actions/create.stub and start adapting it to your needs.

If you run the respective command (e.g., in this case php artisan apiato:generate:action) and choose Create type this would read your specific create.stub file instead of the pre-defined one!

Contributing

If you would like to add your own generators, please check out the Contribution Guide.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/data-caching/index.html b/docs/10.x/core-features/data-caching/index.html index 8233473b0..010aa1b71 100644 --- a/docs/10.x/core-features/data-caching/index.html +++ b/docs/10.x/core-features/data-caching/index.html @@ -4,13 +4,13 @@ Data Caching | Apiato - +
Version: 10.x

Data Caching

Enable / Disable Eloquent Query Caching

info

This feature is disabled By default.

To enable it, go to app/Ship/Configs/repository.php config file and set cache > enabled => true, or set it from the .env file using ELOQUENT_QUERY_CACHE.

More details can be found here.

Users can skip the query caching and request new data by passing specific parameter to the Endpoint. Checkout its documentation here.

Change different caching settings

You can use different cache setting for each repository.

To set cache settings on each repository, first the caching must be enabled, second you need to set some properties on the repository class to override the default values.

For more details about all the properties refer to the L5 repository package documentation.

Note: you don't need to use the CacheableRepository trait or implement the CacheableInterface since they both exist on the Abstract repository class (App\Ship\Parents\Repositories\Repository).

- + \ No newline at end of file diff --git a/docs/10.x/core-features/default-endpoints/index.html b/docs/10.x/core-features/default-endpoints/index.html index 78622cb80..ebaa73272 100644 --- a/docs/10.x/core-features/default-endpoints/index.html +++ b/docs/10.x/core-features/default-endpoints/index.html @@ -4,13 +4,13 @@ Default Endpoints | Apiato - +
Version: 10.x

Default Endpoints

Apiato comes loaded with many useful API endpoints for speeding up the development process.

You can see the endpoints in three ways:

  • In Terminal, by running php artisan route:list -c.
  • In Browser, by generating the beautiful detailed documentation. See API Docs Generator.
  • In Code, by navigating to the Routes folder of each container's UI.
- + \ No newline at end of file diff --git a/docs/10.x/core-features/etag/index.html b/docs/10.x/core-features/etag/index.html index e7c7f2fbf..a8da6ed38 100644 --- a/docs/10.x/core-features/etag/index.html +++ b/docs/10.x/core-features/etag/index.html @@ -4,7 +4,7 @@ ETag | Apiato - + @@ -12,7 +12,7 @@
Version: 10.x

ETag

ETag Middleware

Apiato provides an ETag Middleware (app/Ship/Middlewares/Http/ProcessETagHeadersMiddleware.php) that implements the Shallow technique. It can be used to reduce bandwidth on the client side (especially for Mobile devices).

Enable / Disable ETag

info

This feature is disabled By default.

To enable it go to app/Ship/Configs/apiato.php and set use-etag to true. Of course your client should send the If-None-Match HTTP Header (= etag) in his request for this feature to work properly.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/hash-id/index.html b/docs/10.x/core-features/hash-id/index.html index 53db3c44f..c742ee380 100644 --- a/docs/10.x/core-features/hash-id/index.html +++ b/docs/10.x/core-features/hash-id/index.html @@ -4,13 +4,13 @@ Hash ID | Apiato - +
Version: 10.x

Hash ID

Hashing your internal ID's, is a very helpful feature for security reasons (to prevent some hack attacks) and business reasons (to hide the real total records from your competitors).

Enable Hash ID

Set the HASH_ID=true in the .env file.

Also, with the feature make sure to always use the getHashedKey() on any model, whenever you need to return an ID (mainly from transformers) weather hashed ID or not.

Example:


'id' => $user->getHashedKey(),

Note: if the feature is set to false HASH_ID=false the getHashedKey() will return the normal ID.

Usage

There are 2 ways an ID's can be passed to your system via the API:

In URL example: www.apiato.test/items/abcdef.

In parameters example: [GET] or [POST] www.apiato.test/items?id=abcdef.

in both cases you will need to inform your API about what's coming form the Request class.

Checkout the Requests page. After setting the $decode and $urlParameters properties on your Request class, the ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller. ($request->id will return the decoded ID)

Configuration

You can change the default length and characters used in the ID from the config file app/Ship/Configs/hashids.phpor in the .env file by editing the HASH_ID_LENGTH value.

You can set the HASH_ID_KEY in the .env file to any random string. You can generate this from any of the online random string generators, or run head /dev/urandom | tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_{|}~' | head -c 32 ; echo on the linux command-line. Apiato defaults to the APP_KEY should this not be set.

The HASH_ID_KEY acts as the salt during hashing of the ID. This should never be changed in production as it renders all previously generated IDs impossible to decode.

Testing

In your tests you must hash the ID's before making the calls, because if you tell your Request class to decode an ID for you, it will throw an exception when the ID is not encoded.

for Parameter ID's

Always use getHashedKey() on your models when you want to get the ID

Example:

$data = [
'roles_ids' => [
$role1->getHashedKey(),
$role2->getHashedKey(),
],
'user_id' => $randomUser->getHashedKey(),
];
$response = $this->makeCall($data);

Or you can do this manually Hashids::encode($id);.

for URL ID's

You can use this helper function injectId($id, $skipEncoding = false, $replace = '{id}').

Example:

$response = $this->injectId($admin->id)->makeCall();

More details on the Tests Helpers page.

Availability

You can use the Apiato\Core\Traits\HashIdTrait on any model or class, in order to have the encode and decode functions.

By default, you have access to these functions $this->encode($id) and $this->decode($id) from all your Test classes and Controllers.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/pagination/index.html b/docs/10.x/core-features/pagination/index.html index e995433fb..b567e0acd 100644 --- a/docs/10.x/core-features/pagination/index.html +++ b/docs/10.x/core-features/pagination/index.html @@ -4,7 +4,7 @@ Pagination | Apiato - + @@ -16,7 +16,7 @@ you can do it either project wide or per repository. After that a request can get all the data (with no pagination applied) by applying limit=0.

This will return all matching entities:
api.domain.test/endpoint?limit=0

Project Wide

Set PAGINATION_SKIP=true in .env file.

Per Repository

Override the $allowDisablePagination property in your specific Repository class.

note

Per repository configs override the global config and have precedence.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/profiler/index.html b/docs/10.x/core-features/profiler/index.html index 7a051762a..8ad9aa46f 100644 --- a/docs/10.x/core-features/profiler/index.html +++ b/docs/10.x/core-features/profiler/index.html @@ -4,14 +4,14 @@ Profiler | Apiato - +
Version: 10.x

Profiler

Profiling is very important to optimize the performance of your application, and help you better understand what happens when a request is received, as well as it can speed up the debugging process.

Apiato uses the third-party package laravel-debugbar (which uses the PHP Debug Bar), to collect the profiling data.

By default, the laravel-debugbar package displays the profiling data in the browser. However, Apiato uses a middleware app/Ship/Middlewares/Http/ProfilerMiddleware.php to append the profiling data to the response.

Sample Profiler response

{
// Actual Response Here...
"_profiler": {
"__meta": {
"id": "X167f293230e3457f1bbd95d9c82aba4a",
"datetime": "2017-09-22 18:45:27",
"utime": 1506105927.799299,
"method": "GET",
"uri": "/",
"ip": "172.20.0.1"
},
"messages": {
"count": 0,
"messages": []
},
"time": {
"start": 1506105922.742068,
"end": 1506105927.799333,
"duration": 5.057265043258667,
"duration_str": "5.06s",
"measures": [
{
"label": "Booting",
"start": 1506105922.742068,
"relative_start": 0,
"end": 1506105923.524004,
"relative_end": 1506105923.524004,
"duration": 0.7819359302520752,
"duration_str": "781.94ms",
"params": [],
"collector": null
},
{
"label": "Application",
"start": 1506105923.535343,
"relative_start": 0.7932748794555664,
"end": 1506105927.799336,
"relative_end": 0.00000286102294921875,
"duration": 4.26399302482605,
"duration_str": "4.26s",
"params": [],
"collector": null
}
]
},
"memory": {
"peak_usage": 13234248,
"peak_usage_str": "12.62MB"
},
"exceptions": {
"count": 0,
"exceptions": []
},
"route": {
"uri": "GET /",
"middleware": "api, throttle:30,1",
"domain": "http://api.apiato.test",
"as": "apis_root_page",
"controller": "App\\Containers\\Welcome\\UI\\API\\Controllers\\Controller@apiRoot",
"namespace": "App\\Containers\\Welcome\\UI\\API\\Controllers",
"prefix": "/",
"where": [],
"file": "app/Containers/Welcome/UI/API/Controllers/Controller.php:20-25"
},
"queries": {
"nb_statements": 0,
"nb_failed_statements": 0,
"accumulated_duration": 0,
"accumulated_duration_str": "0μs",
"statements": []
},
"swiftmailer_mails": {
"count": 0,
"mails": []
},
"logs": {
"count": 3,
"messages": [
{
"message": "...",
"message_html": null,
"is_string": false,
"label": "error",
"time": 1506105927.694807
},
{
"message": "...",
"message_html": null,
"is_string": false,
"label": "error",
"time": 1506105927.694811
},
{
"message": "[2017-09-18 17:38:15] testing.INFO: New User registration. ID = 970ylqvaogmxnbdr | Email = apiato@mail.test. Thank you for signing up.\n</div>\n</body>\n</html>\n \n",
"message_html": null,
"is_string": false,
"label": "info",
"time": 1506105927.694812
}
]
},
"auth": {
"guards": {
"web": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]",
"api": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]"
},
"names": ""
},
"gate": {
"count": 0,
"messages": []
}
}
}

Configuration

By default, the profiler feature is turned off. To turn it on edit the .env file and set DEBUGBAR_ENABLED=true.

To control and modify the profiler response, you need to edit this config file app/Ship/Configs/debugbar.php.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/query-parameters/index.html b/docs/10.x/core-features/query-parameters/index.html index fd40007c7..c5762f3f1 100644 --- a/docs/10.x/core-features/query-parameters/index.html +++ b/docs/10.x/core-features/query-parameters/index.html @@ -4,7 +4,7 @@ Query Parameters | Apiato - + @@ -25,7 +25,7 @@ accepts driver as relationship ($availableIncludes in Transformer).

Nested Includes

It is also possible to request "nested includes". Extend the example from above. Imagine, that a Driver may also have a relationship to an Address object. You can access this information as well by calling ?include=driver,driver.address.

Of course, the address include is defined in the respective DriverTransformer that is used here.

Where to define the includes:

Every Transformer can have 2 types of includes $availableIncludes and $defaultIncludes:

    protected $availableIncludes = [
'products',
'store',
'recipients',
];

protected $defaultIncludes = [
'invoice',
];

$defaultIncludes will not be listed in the response, only the $availableIncludes will be.

Visit the Transformers page for more details.

Skip caching

(provided by the L5 Repository)

Note: You need to turn the Eloquent Query Caching ON for this feature to work. ELOQUENT_QUERY_CACHE=true in .env.

To run a new query and force disabling the cache on certain endpoints, you can use this parameter

?skipCache=true

It's not recommended to keep skipping cache as it has bad impact on the performance.

Configuration

Most of these parameters are provided by the L5 Repository and configurable from the Ship/Configs/repository.php file. Some of them are built in house, or inherited from other packages such as Fractal.

See the Query parameters from the User Developer perspective

1) Generate the Default API documentation

2) Visit the documentation URL

More details in the API Docs Generator page.

More Information

For more details on these parameters check out these links:

- + \ No newline at end of file diff --git a/docs/10.x/core-features/rate-limiting/index.html b/docs/10.x/core-features/rate-limiting/index.html index f4023b703..57c7aa16e 100644 --- a/docs/10.x/core-features/rate-limiting/index.html +++ b/docs/10.x/core-features/rate-limiting/index.html @@ -4,14 +4,14 @@ Rate Limiting | Apiato - +
Version: 10.x

Rate Limiting

Apiato uses the default Laravel middleware for rate limiting (throttling).

All REST API requests are throttled to prevent abuse and ensure stability. The exact number of calls that your application can make per day varies based on the type of request you are making.

The rate limit window is 1 minute per endpoint, with most individual calls allowing for 30 requests in each window.

In other words, each user is allowed to make 30 calls per endpoint every 1 minute. (For each unique access token).

To update these values go to app/Ship/Configs/apiato.php config file, or to the ENV file.

'throttle' => [
'enabled' => env('API_RATE_LIMIT_ENABLED', true),
'attempts' => env('API_RATE_LIMIT_ATTEMPTS', '30'),
'expires' => env('API_RATE_LIMIT_EXPIRES', '1'),
]
API_RATE_LIMIT_ENABLED=true
API_RATE_LIMIT_ATTEMPTS=30
API_RATE_LIMIT_EXPIRES=1

For how many hits you can preform on an endpoint, you can always check the header:

X-RateLimit-Limit →30
X-RateLimit-Remaining →29

Enable/Disable Rate Limiting:

The API rate limiting middleware is enabled and applied to all the Container Endpoints by default.

To disable it set API_RATE_LIMIT_ENABLED to false in the .env file.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/useful-commands/index.html b/docs/10.x/core-features/useful-commands/index.html index 160a1d59c..4faa5c00a 100644 --- a/docs/10.x/core-features/useful-commands/index.html +++ b/docs/10.x/core-features/useful-commands/index.html @@ -4,14 +4,14 @@ Useful Commands | Apiato - +
Version: 10.x

Useful Commands

Apiato is loaded with many useful commands to help you speed up the development process. You can see list of all commands, by typing php artisan and look for Apiato section.

Available Commands

  • php artisan apiato Display the current Apiato version.
  • php artisan apiato:apidoc Generate API Documentations with apidoc from your routes Docblock. More details.
  • php artisan apiato:create:admin Create a new User with the ADMIN role
  • php artisan apiato:generate:{component} Generate a specific component for the framework (e.g., Action, Task, ...). For more details on the Code Generator click here.
  • php artisan apiato:list:actions List all Actions in the Application.
  • php artisan apiato:list:tasks List all Tasks in the Application.
  • php artisan apiato:permissions:toRole Give all system Permissions to a specific Role.
  • php artisan apiato:seed-deploy Seeds your custom deployment data from app/Ship/Seeders/SeedDeploymentData.php.
  • php artisan apiato:seed-test Seeds your custom testing data from app/Ship/Seeders/SeedTestingData.php.
  • php artisan apiato:welcome Just saying welcome from a container.
- + \ No newline at end of file diff --git a/docs/10.x/core-features/user-registration/index.html b/docs/10.x/core-features/user-registration/index.html index fde0f70e4..961bd779c 100644 --- a/docs/10.x/core-features/user-registration/index.html +++ b/docs/10.x/core-features/user-registration/index.html @@ -4,13 +4,13 @@ User Registration | Apiato - +
Version: 10.x

User Registration

Register users by credentials (email and passwords)

Call the http://api.apiato.test/v1/register endpoint (you can find its documentation after generating the API Docs.

Check out the registerUser endpoint in the API Routes files.

This will register a new User and respond with user object.

Registration request:

curl --request POST \
--url http://api.apiato.test/v1/register \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'email=john%40doe.com&password=password&name=John%20Doe'

Registration response:

{
"data": {
"object": "User",
"id": "XbPW7awNkzl83LD6",
"name": "John Doe",
"email": "john@doe.com",
"email_verified_at": null,
"gender": null,
"birth": null,
"created_at": "2021-04-15T14:17:24.000000Z",
"updated_at": "2021-04-15T14:17:24.000000Z",
"readable_created_at": "1 second ago",
"readable_updated_at": "1 second ago"
},
"meta": {
"include": [
"roles"
],
"custom": []
}
}

Note: After registration in order to get the user access token you will have to send another call to http://api.example.com/v1/oauth/token with following fields and values

username => your_username e.g. admin@admin.com
password => your_password
grant_type => password
client_id => your_client_id
client_secret => your_client_secret

For third-party clients you must have client ID and secret first. You can generate them by creating new client in your app using Laravel Passport.

For first-party clients you can use a proxy to add those fields on requests coming from your trusted client. For an example on how to do it look at ProxyLoginForWebClientAction Action in Authentication Container.

Register users by Social Account

(Facebook, Twitter, Google..)

Checkout the Social Authentication Page for how to Sign up with Social Account.

- + \ No newline at end of file diff --git a/docs/10.x/core-features/validation/index.html b/docs/10.x/core-features/validation/index.html index 909afd769..ee8606e61 100644 --- a/docs/10.x/core-features/validation/index.html +++ b/docs/10.x/core-features/validation/index.html @@ -4,13 +4,13 @@ Validation | Apiato - +
Version: 10.x

Validation

Apiato uses the powerful Laravel validation system.

In Apiato, validation must be defined in Request component, since every request might have different rules.

Validation rules are automatically applied, once injecting the Request in the Controller.

Requests help validating User data, accessibility, ownership and more.

Example Request with Validation rules:

namespace App\Containers\AppSection\User\UI\API\Requests;

use App\Ship\Parents\Requests\Request;

class RegisterUserRequest extends Request
{
/**
* @return array
*/
public function rules()
{
return [
'email' => 'required|email|max:200|unique:users,email',
'password' => 'required|min:20|max:300',
'name' => 'required|min:2|max:400',
];
}

}

Usage from Controller Example:

    public function registerUser(RegisterUserRequest $request)
{
$user = app(RegisterUserAction::class)->run($request);
return $this->transform($user, UserTransformer::class);
}

Responses

Validation Error response format:

Single Field:

{
"message": "The given data was invalid.",
"errors": {
"email": [
"The email has already been taken."
]
}
}

Multiple Fields:

{
"message": "The given data was invalid.",
"errors": {
"email": [
"The email has already been taken."
],
"password": [
"The password field is required."
]
}
}

More details about requests in the Requests Page.

- + \ No newline at end of file diff --git a/docs/10.x/faq/index.html b/docs/10.x/faq/index.html index 454bae8fa..eb750c57d 100644 --- a/docs/10.x/faq/index.html +++ b/docs/10.x/faq/index.html @@ -4,7 +4,7 @@ Frequently Asked Questions | Apiato - + @@ -37,7 +37,7 @@ Discord.

Lastly, if you got your question answered, consider sharing it, if you believe it can help others. You can submit a PR adding the questions and answer here on the FAQ page. Or leave it somewhere on the repository or on the chat room. Thanks in advance :)

- + \ No newline at end of file diff --git a/docs/10.x/getting-started/container-installer/index.html b/docs/10.x/getting-started/container-installer/index.html index 9fcf0e6bf..273d23ce2 100644 --- a/docs/10.x/getting-started/container-installer/index.html +++ b/docs/10.x/getting-started/container-installer/index.html @@ -4,7 +4,7 @@ Container Installer | Apiato - + @@ -20,7 +20,7 @@ that allows installing/updating containers.
  • You must provide the key extra.apiato.container.name. This key indicates the name of the folder (e.g., container) when installing the package to the /app/ContainersVendorSection folder. In the shown example, the container would be installed to app/Containers/VendorSection/Foo.
  • - + \ No newline at end of file diff --git a/docs/10.x/getting-started/conventions-and-principles/index.html b/docs/10.x/getting-started/conventions-and-principles/index.html index ff1d701fd..a27fe4a85 100644 --- a/docs/10.x/getting-started/conventions-and-principles/index.html +++ b/docs/10.x/getting-started/conventions-and-principles/index.html @@ -4,13 +4,13 @@ Conventions | Apiato - +
    Version: 10.x

    Conventions

    HTTP Methods usage in RESTful API's

    • GET (SELECT): retrieve a specific resource from the server, or a listing of resources.
    • POST (CREATE): create a new resource on the server.
    • PUT (UPDATE): update a resource on the server, providing the entire resource.
    • PATCH (UPDATE): update a resource on the server, providing only changed attributes.
    • DELETE (DELETE): remove a resource from the server.

    Naming Conventions for Routes & Actions

    • GetAllResource: to fetch all resources.
    • FindResourceByID: to search for single resource by its unique identifier.
    • CreateResource: to create a new resource.
    • UpdateResource: to update/edit existing resource.
    • DeleteResource: to delete a resource.

    General guidelines and principles for RESTful URLs

    • A URL identifies a resource.
    • URLs should include nouns, not verbs.
    • Use plural nouns only for consistency (no singular nouns).
    • Use HTTP verbs (GET, POST, PUT, DELETE) to operate on the collections and elements.
    • You should not need to go deeper than resource/identifier/resource.
    • Put the version number at the base of your URL, for example http://apiato.test/v1/path/to/resource.
    • If an input data changes the logic of the endpoint, it should be passed in the URL. If not can go in the header "like Auth Token".
    • Don't use query parameters to alter state.
    • Don't use mixed-case paths if you can help it; lowercase is best.
    • Don't use implementation-specific extensions in your URIs (.php, .py, .pl, etc.)
    • Limit your URI space as much as possible. And keep path segments short.
    • Don't put metadata in the body of a response that should be in a header

    Good URL examples

    • Find a single Car by its unique identifier (ID):
      • GET http://www.api.apiato.test/v1/cars/123
    • Get all Cars:
      • GET http://www.api.apiato.test/v1/cars
    • Find/Search cars by one or more fields:
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes;color:white
    • Order and Sort query result:
      • GET http://www.api.apiato.test/v1/cars?orderBy=created_at&sortedBy=desc
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes&orderBy=created_at&sortedBy=desc
    • Specify optional fields:
      • GET http://www.api.apiato.test/v1/cars?filter=id;name;status
      • GET http://www.api.apiato.test/v1/cars/123?filter=id;name;status
    • Get all Drivers belonging to a Car:
      • GET http://www.api.apiato.test/v1/cars/123/drivers
      • GET http://www.api.apiato.test/v1/cars/123/drivers/123/addresses
    • Include Drivers objects relationship with the car response:
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers,owner
    • Add new Car:
      • POST http://www.api.apiato.test/v1/cars
    • Add new Driver to a Car:
      • POST http://www.api.apiato.test/v1/cars/123/drivers

    General principles for HTTP methods

    • Don't ever use GET to alter state; to prevent Googlebot from corrupting your data. And use GET as much as possible.
    • Don't use PUT unless you are updating an entire resource. And unless you can also legitimately do a GET on the same URI.
    • Don't use POST to retrieve information that is long-lived or that might be reasonable to cache.
    • Don't perform an operation that is not idempotent with PUT.
    • Use GET for things like calculations, unless your input is large, in which case use POST.
    • Use POST in preference to PUT when in doubt.
    • Use POST whenever you have to do something that feels RPC-like.
    • Use PUT for classes of resources that are larger or hierarchical.
    • Use DELETE in preference to POST to remove resources.
    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/installation/index.html b/docs/10.x/getting-started/installation/index.html index e6e41c37b..1beeb6a29 100644 --- a/docs/10.x/getting-started/installation/index.html +++ b/docs/10.x/getting-started/installation/index.html @@ -4,7 +4,7 @@ Installation | Apiato - + @@ -29,7 +29,7 @@ try running this command homestead halt && homestead up --provision.

    Using anything else

    If you're not into virtualization solutions, you can set up your environment directly on your machine. Check the software's requirements list.

    Let's Play

    Now let's see it in action

    Open your web browser and visit:

    • http://apiato.test You should see an HTML page, with Apiato in the middle.
    • http://api.apiato.test You should see a response like this:
    [
    "Welcome to Apiato"
    ]

    Open your HTTP client and call:

    • http://api.apiato.test/ You should see a JSON response with message: "Welcome to apiato.",
    • http://api.apiato.test/v1 You should see a JSON response with message: "Welcome to apiato (API V1).",

    Make some HTTP calls to the API:

    To make the calls you can use Postman, HTTPIE or any other tool you prefer.

    Let's test the (user registration) endpoint http://api.apiato.test/v1/register with cURL:

    curl -X POST -H "Accept: application/json" -H "Cache-Control: no-cache" -F "email=John@Doe.me" -F "password=so-secret" -F "name=John Doe" "http://api.apiato.test/v1/register"

    You should get a response like this:

    Header:

    Access-Control-Allow-Origin → ...
    Cache-Control → ...
    Connection → keep-alive
    Content-Language → en
    Content-Type → application/json
    Date → Wed, 11 Apr 2000 22:55:88 GMT
    Server → nginx
    Transfer-Encoding → chunked
    Vary → Origin
    X-Powered-By → PHP/7.7.7
    X-RateLimit-Limit → 30
    X-RateLimit-Remaining → 29

    Body:

    {
    "data": {
    "object": "User",
    "id": "7VgmkMw7rR2pWO5j",
    "name": "John Doe",
    "email": "John@Doe.me",
    "email_verified_at": null,
    "gender": null,
    "birth": null,
    "created_at": "2021-04-12T13:33:24.000000Z",
    "updated_at": "2021-04-12T13:33:24.000000Z",
    "readable_created_at": "1 second ago",
    "readable_updated_at": "1 second ago"
    },
    "meta": {
    "include": [
    "roles"
    ],
    "custom": []
    }
    }
    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/markdown-features/index.html b/docs/10.x/getting-started/markdown-features/index.html index 9458c591a..c66a38afe 100644 --- a/docs/10.x/getting-started/markdown-features/index.html +++ b/docs/10.x/getting-started/markdown-features/index.html @@ -4,13 +4,13 @@ Markdown Features | Apiato - +
    Version: 10.x

    Markdown Features

    Docusaurus supports the Markdown syntax and has some additional features.

    Front Matter

    Markdown documents can have associated metadata at the top called Front Matter:

    ---
    id: my-doc
    title: My document title
    description: My document description
    sidebar_label: My doc
    ---

    Markdown content

    Regular Markdown links are supported using url paths or relative file paths.

    Let's see how to [Create a page].
    Let's see how to [Create a page].

    Let's see how to [Create a page].

    Markdown images

    Regular Markdown images are supported.

    Add an image at static/img/logo.png and use this Markdown declaration:

    ![Docusaurus logo](/img/logo.png)

    Docusaurus logo

    Code Blocks

    Markdown code blocks are supported with Syntax highlighting.

    ```jsx title="src/components/HelloDocusaurus.js"
    function HelloDocusaurus() {
    return (
    <h1>Hello, Docusaurus!</h1>
    )
    }
    ```
    src/components/HelloDocusaurus.js
    function HelloDocusaurus() {
    return <h1>Hello, Docusaurus!</h1>;
    }

    Admonitions

    Docusaurus has a special syntax to create admonitions and callouts:

    :::tip My tip

    Use this awesome feature option

    :::

    :::danger Take care

    This action is dangerous

    :::
    My tip

    Use this awesome feature option

    Take care

    This action is dangerous

    React components

    Thanks to MDX, you can make your doc more interactive and use React components inside Markdown:

    export const Highlight = ({children, color}) => (
    <span
    style={{
    backgroundColor: color,
    borderRadius: '2px',
    color: 'red',
    padding: '0.2rem',
    }}>
    {children}
    </span>
    );

    <Highlight color="#25c2a0">Docusaurus green</Highlight> and <Highlight color="#1877F2">Facebook blue</Highlight> are my favorite colors.
    Docusaurus green and Facebook blue are my favorite colors.
    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/requests/index.html b/docs/10.x/getting-started/requests/index.html index c7b38b6e6..d0c5eeea0 100644 --- a/docs/10.x/getting-started/requests/index.html +++ b/docs/10.x/getting-started/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -17,7 +17,7 @@ you can force your users to send application/json by setting 'force-accept-header' => true, in app/Ship/Configs/apiato.php or allow them to skip it completely by setting the 'force-accept-header' => false,. By default this flag is set to false.

    Calling Endpoints

    Calling unprotected endpoint example:

    curl -X POST -H "Accept: application/json" -H "Content-Type: application/x-www-form-urlencoded; -F "email=admin@admin.com" -F "password=admin" -F "=" "http://api.domain.test/v2/register"

    Calling protected endpoint (passing Bearer Token) example:

    curl -X GET -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "http://api.domain.test/v1/users"
    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/responses/index.html b/docs/10.x/getting-started/responses/index.html index de3b7533b..a0105219a 100644 --- a/docs/10.x/getting-started/responses/index.html +++ b/docs/10.x/getting-started/responses/index.html @@ -4,7 +4,7 @@ Responses | Apiato - + @@ -17,7 +17,7 @@ If no $resourceKey is defined at the Model, the ShortClassName is used as key. For example, the ShortClassName of the App\Containers\AppSection\User\Models\User::class is User.

    Error Responses formats

    Visit each feature, e.g. the Authentication and there you will see how an unauthenticated response looks like, same for Authorization, Validation and so on.

    Building a Responses from the Controller:

    Checkout the Controller response builder helper functions.

    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/samples/index.html b/docs/10.x/getting-started/samples/index.html index 58cddc0d3..84d3b5207 100644 --- a/docs/10.x/getting-started/samples/index.html +++ b/docs/10.x/getting-started/samples/index.html @@ -4,7 +4,7 @@ Samples | Apiato - + @@ -12,7 +12,7 @@
    Version: 10.x

    Samples

    The basic flow

    When an HTTP request is received, it first hits your predefined Endpoint (each endpoint live in its own Route file).

    Sample Route Endpoint

    Route::get('/hello', [Controller::class, 'sayHello']);

    After the user makes a request to the endpoint [GET] www.api.apiato.com/v1/hello it calls the defined controller function (sayHello).

    Sample Controller Function

    class Controller extends ApiController
    {
    public function sayHello(SayHelloRequest $request)
    {
    $helloMessage = app(SayHelloAction::class)->run();

    $this->json([
    $helloMessage
    ]);
    }
    }

    This function takes a Request class SayHelloRequest to automatically checks if the user has the right access to this endpoint. Only if the user has access, it proceeds to the function body.

    Then the function calls an Action (SayHelloAction) to perform the business logic.

    Sample Action

    class SayHelloAction extends Action
    {
    public function run()
    {
    return 'Hello World!';
    }
    }

    The Action can do anything then return a result (could be an Object, a String or anything).

    When the Action finishes its job, the controller function gets ready to build a response.

    Json responses can be built using the helper function json ($this->json(['foo' => 'bar']);).

    Sample User Response

    [
    "Hello World!"
    ]
    - + \ No newline at end of file diff --git a/docs/10.x/getting-started/software-architectural-patterns/index.html b/docs/10.x/getting-started/software-architectural-patterns/index.html index bfd9891ef..19dd4c111 100644 --- a/docs/10.x/getting-started/software-architectural-patterns/index.html +++ b/docs/10.x/getting-started/software-architectural-patterns/index.html @@ -4,7 +4,7 @@ Architecture | Apiato - + @@ -14,7 +14,7 @@ App\Containers\SectionName\Printer).
  • Container MAY be named anything, however a good practice is to name it to its most important Model name. Example: If the User Story is (User can create a Store and Store can have Items) then we you could have 3 Containers (User, Store and Item).
  • - + \ No newline at end of file diff --git a/docs/10.x/index.html b/docs/10.x/index.html index 283e8475d..04d36198b 100644 --- a/docs/10.x/index.html +++ b/docs/10.x/index.html @@ -4,13 +4,13 @@ Requirements | Apiato - +
    Version: 10.x

    Requirements

    Requirements

    • GIT
    • PHP >= 7.4 (8.0 is recommended)
    • Composer
    • PHP Extensions:
      • OpenSSL PHP Extension
      • PDO PHP Extension
      • Mbstring PHP Extension
      • Tokenizer PHP Extension
      • BCMath PHP Extension (required when the Hash ID feature is enabled)
      • Intl Extension (required when you use the Localization Container)
    - + \ No newline at end of file diff --git a/docs/10.x/main-components/actions/index.html b/docs/10.x/main-components/actions/index.html index fe77cbffb..ee6be876d 100644 --- a/docs/10.x/main-components/actions/index.html +++ b/docs/10.x/main-components/actions/index.html @@ -4,13 +4,13 @@ Actions | Apiato - +
    Version: 10.x

    Actions

    Definition & Principles

    Read Porto SAP Documentation (#Actions).

    Rules

    • All Actions MUST extend App\Ship\Parents\Actions\Action.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Actions
    - CreateUserAction.php
    - DeleteUserAction.php
    - ...

    Code Sample

    Action

    class CreateAdminAction extends Action
    {
    public function run(string $email, string $password, string $name, bool $isClient = false): User
    {
    $admin = app(CreateUserByCredentialsTask::class)->run(
    $isClient,
    $email,
    $password,
    $name
    );

    app(AssignUserToRoleTask::class)->run($admin, ['admin']);

    return $admin;
    }
    }

    Calling multiple Tasks

    class DemoAction extends Action
    {
    public function run($xxx, $yyy, $zzz)
    {
    $foo = app(Sample111Task::class)->run($xxx, $yyy);
    $bar = app(Sample222Task::class)->run($zzz);
    }
    }

    Usage from a Controller

        public function deleteUser(DeleteUserRequest $request)
    {
    $user = app(DeleteUserAction::class)->run($request);
    return $this->deleted($user);
    }
    tip

    The same Action MAY be called by multiple Controllers (Web, Api, Cli).

    - + \ No newline at end of file diff --git a/docs/10.x/main-components/controllers/index.html b/docs/10.x/main-components/controllers/index.html index 23c0c7c5a..b3ccf78f2 100644 --- a/docs/10.x/main-components/controllers/index.html +++ b/docs/10.x/main-components/controllers/index.html @@ -4,7 +4,7 @@ Controllers | Apiato - + @@ -17,7 +17,7 @@ This function allows including metadata in the response.

    $metaData = ['total_credits', 10000];

    return $this->withMeta($metaData)->transform($receipt, ReceiptTransformer::class);

    json This function allows passing array data to be represented as json.

    return $this->json([
    'foo': 'bar'
    ])

    Other functions

    • accepted
    • deleted
    • noContent
    • // Some functions might not be documented, so refer to the vendor/apiato/core/Traits/ResponseTrait.php and see the public functions.
    - + \ No newline at end of file diff --git a/docs/10.x/main-components/exceptions/index.html b/docs/10.x/main-components/exceptions/index.html index a3666ff39..8db119010 100644 --- a/docs/10.x/main-components/exceptions/index.html +++ b/docs/10.x/main-components/exceptions/index.html @@ -4,13 +4,13 @@ Exceptions | Apiato - +
    Version: 10.x

    Exceptions

    Definition & Principles

    Read Porto SAP Documentation (#Exceptions).

    Rules

    • All Exceptions MUST extend App\Ship\Parents\Exceptions\Exception.
    • Shared (general) Exceptions between all Containers SHOULD be created in the Exceptions Ship folder (app/Ship/Exceptions/*).
    • Every Exception SHOULD have two properties code and message. You can override those values while throwing the error.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Exceptions
    - AccountFailedException.php
    - ...

    - Ship
    - Exceptions
    - CreateResourceFailedException.php
    - NotFoundException.php
    - ...

    Code Samples

    Demo Exception

    class DemoException extends Exception
    {
    public $code = Response::HTTP_CONFLICT;
    public $message = 'This is a demo exception.';
    }

    Usage from anywhere

    throw new AccountFailedException();

    Usage with errors

    throw (new AccountFailedException())->withErrors(['email' => 'Email already in use']);
    throw (new AccountFailedException())->withErrors(['email' => ['Email already in use', 'Another message']]);

    Usage with errors and localization

    For localization, you can use the Localization Container

    // translation strings are automatically translated if the translations are found.
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken']);

    Response:

    {
    "message": "The exception error message.",
    "errors": {
    "email": [
    "The email has already been taken."
    ]
    }
    }

    Usage with Log for Debugging

    throw (new AccountFailedException())->debug($e); // debug() accepts string or \Exception instance

    Usage and overriding the default

    throw new AccountFailedException('I am the message to be displayed to the user');
    - + \ No newline at end of file diff --git a/docs/10.x/main-components/models/index.html b/docs/10.x/main-components/models/index.html index c9fa59c54..4375217d5 100644 --- a/docs/10.x/main-components/models/index.html +++ b/docs/10.x/main-components/models/index.html @@ -4,13 +4,13 @@ Models | Apiato - +
    Version: 10.x

    Models

    Definition & Principles

    Read Porto SAP Documentation (#Models).

    Rules

    • All Models MUST extend from App\Ship\Parents\Models\Model.
    • If the name of a model differs from the Container name you have to implement model() method in the repository - more details.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Models
    - User.php
    - UserId.php
    - ...

    Code Sample

    class Demo extends Model
    {
    protected $table = 'demos';

    protected $fillable = [
    'label',
    'user_id'
    ];

    protected $hidden = [
    'token',
    ];

    protected $casts = [
    'total_credits' => 'float',
    ];

    protected $dates = [
    'created_at',
    'updated_at',
    ];

    public function user()
    {
    return $this->belongsTo(\App\Containers\AppSection\User\Models\User::class);
    }
    }

    Notice the Demo Model has a relationship with User Model, which lives in another Container.

    Casts

    The casts attribute can be used to parse any of the model's attributes to a specific type. In the code sample below we can cast total_credits to float.

    More information about the applicable cast-types can be found in the laravel eloquent-mutators documentation.

    You can place any dates inside the $dates to parse those automatically.

    - + \ No newline at end of file diff --git a/docs/10.x/main-components/requests/index.html b/docs/10.x/main-components/requests/index.html index 74771548a..84e26f37d 100644 --- a/docs/10.x/main-components/requests/index.html +++ b/docs/10.x/main-components/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -25,7 +25,7 @@ function.

    This helper, in turn, allows to "redefine" keys in the request for subsequent processing. Consider the following example request:

    {
    "data" : {
    "name" : "John Doe"
    }
    }

    Your Task to process this data, however, requests the field data.name as data.username. You can call the helper like this:

    $request->mapInput([
    'data.name' => 'data.username',
    ]);

    The resulting structure would look like this:

    {
    "data" : {
    "username" : "John Doe"
    }
    }

    Storing Data on the Request

    During the Request life-cycle you may want to store some data on the request object and pass it to other SubActions (or Tasks).

    To store some data on the request use:

    $request->keep(['someKey' => $someValue]);

    To retrieve the data back at any time during the request life-cycle use:

    $someValue = $request->retrieve('someKey');

    Unit Testing for Actions (Request)

    Since we're passing Request objects to Actions. When writing unit tests we need to create fake Request just to pass it to the Action with some fake data.

    // creating
    $request = RegisterUserRequest::injectData($data);

    Example One:

    $data = [
    'email' => 'john@doe.test',
    'name' => 'John Doe',
    'password' => 'so-secret',
    ];

    // create request object with some data
    $request = RegisterUserRequest::injectData($data);

    // create instance of the Action
    $action = app(RegisterUserAction::class)->run($request);

    // do any kind of assertions..
    $this->assertInstanceOf(User::class, $user);

    Example Two (With Authenticated User):

    $data = [
    'store_id' => $this->encode($store->id),
    'items' => $orderItems,
    'recipient' => $receipient,
    ];

    $user = User::factory()->create();

    $request = MakeOrderRequest::injectData($data, $user);

    $order = app(MakeOrderAction::class)->run($request);
    - + \ No newline at end of file diff --git a/docs/10.x/main-components/routes/index.html b/docs/10.x/main-components/routes/index.html index 2f2f02953..1989d5c46 100644 --- a/docs/10.x/main-components/routes/index.html +++ b/docs/10.x/main-components/routes/index.html @@ -4,13 +4,13 @@ Routes | Apiato - +
    Version: 10.x

    Routes

    Definition & Principles

    Read Porto SAP Documentation (#Routes).

    Rules

    • API Route files MUST be named according to their API's version, exposure and functionality. e.g. CreateOrder.v1.public.php, FulfillOrder.v2.public.php, CancelOrder.v1.private.php...

    • Web Route files are pretty similar to API web files, but they can be named anything.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - API
    - Routes
    - CreateItem.v1.public.php
    - DeleteItem.v1.public.php
    - CreateItem.v2.public.php
    - DeleteItem.v1.private.php
    - ApproveItem.v1.private.php
    - ...
    - WEB
    - Routes
    - main.php
    - ...

    Code Sample

    Web & API route

    Routes are defined exactly like the way you defined them in Laravel.

    Route::post('hello', [Controller::class, 'sayHello']);

    Protected Route (API)

    Route::get('users', [Controller::class, 'listAllUsers'])
    ->middleware(['auth:api']);

    Protect your Endpoints:

    Checkout the Authorization Page.

    Difference between Public & Private routes files

    Apiato has 2 types of endpoint, Public (External) mainly for third parties clients, and Private (Internal) for your own Apps. This will help to generate separate documentations for each and keep your internal API private.

    - + \ No newline at end of file diff --git a/docs/10.x/main-components/subactions/index.html b/docs/10.x/main-components/subactions/index.html index c79f63604..88d658e71 100644 --- a/docs/10.x/main-components/subactions/index.html +++ b/docs/10.x/main-components/subactions/index.html @@ -4,13 +4,13 @@ Sub Actions | Apiato - +
    Version: 10.x

    Sub Actions

    Definition & Principles

    Read Porto SAP Documentation (#Sub-Actions).

    Rules

    • All SubActions MUST extend from App\Ship\Parents\Actions\SubAction.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Actions
    - ValidateAddressSubAction.php
    - BuildOrderSubAction.php
    - ...

    Code Sample

    ExampleSubAction

    class ExampleSubAction extends SubAction
    {
    public function run(SomeRequest $request)
    {
    app(SomeTask::class)->run($request);
    }
    }
    note

    Every feature available for Actions, are also available in SubActions.

    - + \ No newline at end of file diff --git a/docs/10.x/main-components/tasks/index.html b/docs/10.x/main-components/tasks/index.html index 7e3d51226..385676d9a 100644 --- a/docs/10.x/main-components/tasks/index.html +++ b/docs/10.x/main-components/tasks/index.html @@ -4,13 +4,13 @@ Tasks | Apiato - +
    Version: 10.x

    Tasks

    Definition & Principles

    Read Porto SAP Documentation (#Tasks).

    Rules

    • All Tasks MUST extend from App\Ship\Parents\Tasks\Task.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Tasks
    - ConfirmUserEmailTask.php
    - GenerateEmailConfirmationUrlTask.php
    - SendConfirmationEmailTask.php
    - ValidateConfirmationCodeTask.php
    - SetUserEmailTask.php
    - ...

    Code Sample

    Task

    class FindUserByIdTask extends Task
    {
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    public function run($id)
    {
    try {
    $user = $this->userRepository->find($id);
    } catch (Exception $e) {
    throw new UserNotFoundException();
    }

    return $user;
    }
    }

    Task usage from an Action

    class ValidateUserEmailByConfirmationCodeAction extends Action
    {
    public function run($userId, $code)
    {
    app(ValidateConfirmationCodeTask::class)->run($userId, $code);
    $user = app(FindUserByIdTask::class)->run($userId);
    app(ConfirmUserEmailTask::class)->run($user);
    }
    }
    - + \ No newline at end of file diff --git a/docs/10.x/main-components/transformers/index.html b/docs/10.x/main-components/transformers/index.html index 552d6c39b..99a7608b7 100644 --- a/docs/10.x/main-components/transformers/index.html +++ b/docs/10.x/main-components/transformers/index.html @@ -4,13 +4,13 @@ Transformers | Apiato - +
    Version: 10.x

    Transformers

    Definition & Principles

    Read Porto SAP Documentation (#Transformers).

    Rules

    • All API responses MUST be formatted via a Transformer.

    • Every Transformer SHOULD extend from App\Ship\Parents\Transformers\Transformer.

    • Each Transformer MUST have a transform() function.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - API
    - Transformers
    - UserTransformer.php
    - ...

    Code Samples

    Reward Transformer with Country relation

    class ItemTransformer extends Transformer
    {
    protected $availableIncludes = [
    'images',
    ];

    protected $defaultIncludes = [
    'roles',
    ];

    public function transform(Item $item)
    {
    $response = [
    'object' => $item->getResourceKey(),
    'id' => $item->getHashedKey(),
    'name' => $item->name,
    'description' => $item->description,
    'price' => (float)$item->price,
    'weight' => (float)$item->weight,
    'created_at' => $item->created_at,
    'updated_at' => $item->updated_at,
    'readable_created_at' => $item->created_at->diffForHumans(),
    'readable_updated_at' => $item->updated_at->diffForHumans(),
    ];

    // add more or modify data for Admins only
    $response = $this->ifAdmin([
    'real_id' => $item->id,
    'deleted_at' => $item->deleted_at,
    ], $response);

    return $response;
    }

    public function includeImages(Item $item)
    {
    return $this->collection($item->images, new ItemImageTransformer());
    }

    public function includeRoles(User $user)
    {
    return $this->collection($user->roles, new RoleTransformer());
    }
    }

    Usage from Controller (Single Item)

    $user = $this->getUser();

    $this->transform($user, UserTransformer::class);

    // more options are available

    Relationships (include)

    Loading relationships in Transformer (calling other Transformers):

    This can be done in 2 ways:

    1. By the User, he can specify what relations to return in response.

    2. By the Developer, define what relations to include at run time.

    From Front-end

    You can request data with their relationships directly from the API call using include=tags,user but first the Transformer need to have the availableIncludes defined with their functions like this:

    class AccountTransformer extends Transformer
    {
    protected $availableIncludes = [
    'tags',
    'user',
    ];

    public function transform(Account $account)
    {
    return [
    'id' => (int)$account->id,
    'url' => $account->url,
    'username' => $account->username,
    'secret' => $account->secret,
    'note' => $account->note,
    ];
    }

    public function includeTags(Account $account)
    {
    // use collection with `multi` relationship
    return $this->collection($account->tags, new TagTransformer());
    }

    public function includeUser(Account $account)
    {
    // use `item` with single relationship
    return $this->item($account->user, new UserTransformer());
    }
    }

    Now to get the Tags with the response when Accounts are requested pass the ?include=tags parameter with the [GET] request.

    To get Tags with User use the comma separator: ?include=tags,user.

    From Back-end

    From the controller you can dynamically set the DefaultInclude using (setDefaultIncludes) anytime you want.

    return $this->transform($rewards, ProductsTransformer::class)->setDefaultIncludes(['tags']);

    You need to have includeTags function defined on the transformer. Look at the full examples above.

    If you want to include a relation with every response from this transformer you can define the relation directly in the transformer on ($defaultIncludes)

    protected $availableIncludes = [
    'users',
    ];

    protected $defaultIncludes = [
    'tags',
    ];

    // ..

    You need to have includeUser and includeTags functions defined on the transformer. Look at the full examples above.

    Transformer Available helper functions:

    • user() : returns current authenticated user object.

    • ifAdmin($adminResponse, $clientResponse) : merges normal client response with the admin extra or modified results, when current authenticated user is Admin. Look at the full examples above.

    For more information about the Transformers read this.

    - + \ No newline at end of file diff --git a/docs/10.x/main-components/views/index.html b/docs/10.x/main-components/views/index.html index 37c31cd36..5bd47cb61 100644 --- a/docs/10.x/main-components/views/index.html +++ b/docs/10.x/main-components/views/index.html @@ -4,13 +4,13 @@ Views | Apiato - +
    Version: 10.x

    Views

    Definition & Principles

    Read Porto SAP Documentation (#Views).

    Rules

    • Views SHOULD be created inside the Containers, and they will be automatically available for use in the Web Controllers.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - WEB
    - Views
    - welcome.php
    - profile.php
    - ...

    Code Sample

    Welcome page View

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome</title>
    </head>
    <body>
    <div class="container">
    <div class="content">
    <div class="title">Welcome</div>
    </div>
    </div>
    </body>
    </html>

    Usage From Controller

    class Controller extends WebController
    {
    public function sayWelcome()
    {
    return view('just-welcome');
    }
    }

    Namespaces

    • By default, all Views are namespaced as the camelCase of its Section name + @ + camelCase of its Container name.

    For example, a view named welcome-page inside MySection > MyContainer can be accessed like this: view(mySection@myContainer::welcome-page)

    If you try to access it without the namespace view('just-welcome'), it will not find your View.

    note

    View files in Ship folder are exception to this and will be namespaced with the word "ship" instead of section name, e.g. view(ship::welcome-page)

    - + \ No newline at end of file diff --git a/docs/10.x/miscellaneous/tasks-queuing/index.html b/docs/10.x/miscellaneous/tasks-queuing/index.html index fbea01091..bbc80b10f 100644 --- a/docs/10.x/miscellaneous/tasks-queuing/index.html +++ b/docs/10.x/miscellaneous/tasks-queuing/index.html @@ -4,7 +4,7 @@ Tasks Queuing | Apiato - + @@ -18,7 +18,7 @@

    The only addition to the Laravel's queues in Apiato, is that by default, apiato detects which queue driver you are planning to use (based on the configs), to create the migration files required, in case type database is used.

    if (Config::get('queue.default') == 'database')
    {
    // do something
    }

    (refer to app/Ship/Migrations/ folder for more details).

    Beanstalkd

    In order to use Beanstalkd as your queue driver, you need to require the "pda/pheanstalk": "^4.0" package first. You can include this in any composer.json file you want.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/miscellaneous/tasks-scheduling/index.html b/docs/10.x/miscellaneous/tasks-scheduling/index.html index 341672989..9cb2d0934 100644 --- a/docs/10.x/miscellaneous/tasks-scheduling/index.html +++ b/docs/10.x/miscellaneous/tasks-scheduling/index.html @@ -4,7 +4,7 @@ Tasks Scheduling | Apiato - + @@ -18,7 +18,7 @@ See the Commands Page.

    Once you have your command ready, go to app/Ship/Kernels/ConsoleKernel.php and start adding the commands you need to schedule inside the schedule function.

    Example:

    protected function schedule(Schedule $schedule)
    {
    $schedule->command('apiato:welcome')->everyMinute();
    $schedule->job(new myJob)->hourly();
    $schedule->exec('touch me.txt')->dailyAt('12:00');
    // ...
    }

    More details here.

    note

    You do not need to register the commands with the $commands property or point to them in the commands() function. Apiato will do that automatically for you.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/miscellaneous/tests-helpers/index.html b/docs/10.x/miscellaneous/tests-helpers/index.html index 1ed79c62c..779019573 100644 --- a/docs/10.x/miscellaneous/tests-helpers/index.html +++ b/docs/10.x/miscellaneous/tests-helpers/index.html @@ -4,7 +4,7 @@ Tests Helpers | Apiato - + @@ -24,7 +24,7 @@ testing data.

    1. Go to app/Ship/Seeder/SeedTestingData.php seeder class, and create your live testing data.

    2. Run this command php artisan apiato:seed-test

    Debugging with PsySH

    For better debugging and development, you can open a runtime developer console while executing your test.

    Using PsySH (interactive debugger and REPL "read-eval-print loop" for PHP). The package is required by the Laravel Tinker Package.

    To use it set the breakpoint eval(\Psy\sh()); anywhere you want in any Actions, Controllers, Tasks... and run your test normally.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/commands/index.html b/docs/10.x/optional-components/commands/index.html index 484bd6fd3..6c8beea38 100644 --- a/docs/10.x/optional-components/commands/index.html +++ b/docs/10.x/optional-components/commands/index.html @@ -4,13 +4,13 @@ Commands | Apiato - +
    Version: 10.x

    Commands

    Definition

    • Commands are a Laravel artisan command. Laravel has its own default commands, and you can create your own as well.
    • Commands provide a way to interact with the Laravel app.
    • A Command can be scheduled by a Task scheduler, like Cron Job or by the Laravel built-in wrapper of the Cron Job "laravel scheduler".
    • Commands could be Closure based or Classes.
    • "dispatch" is the term that is usually used to call a Command.

    Principles

    • Containers MAY or MAY NOT have one or more Commands.

    • Every Command SHOULD call an Action to perform its job, and should not contain any business logic.

    • Ship may contain Application general Commands.

    Rules

    • All Commands MUST extend from App\Ship\Parents\Commands\ConsoleCommand.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - CLI
    - Commands
    - SayHelloCommand.php
    - ...
    - Ship
    - Commands
    - GeneralCommand.php
    - ...

    Code Samples

    A Simple Command

    class HelloWorldCommand extends ConsoleCommand
    {
    protected $signature = 'hello:world';
    protected $description = 'Hello World!';

    public function handle()
    {
    echo "Hello World :)\n";
    }
    }

    Usage from CLI (Terminal)

    php artisan hello:world

    Schedule Commands Execution

    To Schedule the execution of a Command checkout the Tasks Scheduling page.

    Define Consoles Routes

    To define Console route go to app/Ship/Commands/Routes.php.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/configs/index.html b/docs/10.x/optional-components/configs/index.html index cba66e9f6..d24b2ba31 100644 --- a/docs/10.x/optional-components/configs/index.html +++ b/docs/10.x/optional-components/configs/index.html @@ -4,13 +4,13 @@ Configs | Apiato - +
    Version: 10.x

    Configs

    Definition

    Configs are files that contain configurations.

    In each Apiato container, there are two types of config files:

    • the container specific config file (a config file that contains the container specific configurations).
    • the container third party packages config files (a config file that belongs to a third party package, required by the composer file of the container).

    Principles

    • Your custom config files and third party packages config files, should be placed in the Container, unless it's too generic then it can be placed on the Ship Layer.
    • Containers can have as many config files as they need.

    Rules

    • When publishing a third party package config file, move it manually to its container or to the Ship Configs folder in case it is generic.
    • Framework config files (provided by Laravel) lives at the default config directory on the root of the project.
    • You SHOULD NOT add any config file to the root config directory.
    • The container specific config file, MUST be named this way:
      camelCase of its Section name + - + camelCase of its Container name, to prevent conflicts between third party packages and container specific packages.
      For example, config file inside MySection > MyContainer should be named like this: mySection-myContainer.php

    Folder Structure

    - app
    - Containers
    {section-name}
    - {container-name}
    - Configs
    - {section-name}-{container-name}.php
    - package-config-file1.php
    - ...
    - Ship
    - Configs
    - apiato.php
    - ...
    - config
    - app.php
    - ...

    Code Samples

    Example simple Config file

    // app/Containers/{SectionName}/{ContainerName}/Configs/{section-name}-{container-name}.php
    return [

    /*
    |--------------------------------------------------------------------------
    | Default Namespace
    |--------------------------------------------------------------------------
    */
    'namespace' => 'App',

    // some other config params here...

    You can access the respective configuration key like this:

    $value = Config::get('{section-name}-{container-name}.namespace');     // returns 'App'
    $value = config('{section-name}-{container-name}.namespace'); // same, but using laravel helper function
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/criterias/index.html b/docs/10.x/optional-components/criterias/index.html index e866af0bd..4e67864c7 100644 --- a/docs/10.x/optional-components/criterias/index.html +++ b/docs/10.x/optional-components/criterias/index.html @@ -4,13 +4,13 @@ Criterias | Apiato - +
    Version: 10.x

    Criterias

    Definition

    Criterias are classes that hold and apply query condition when retrieving data from the database through a Repository.

    Without using a Criteria class, you can add your query conditions to a Repository or to a Model as scope, but with Criterias, your query conditions can be shared across multiple Models and Repositories. It allows you to define the query condition once and use it anywhere in the App.

    Principles

    • Every Container MAY have its own Criterias. However, shared Criterias SHOULD be created in the Ship layer.

    • A Criteria MUST not contain any extra code, if it needs data, the data SHOULD be passed to it from the Actions or the Task. It SHOULD not call any Task for data.

    Rules

    • All Criterias MUST extend from App\Ship\Parents\Criterias\Criteria.

    • Every Criteria SHOULD have an apply() function.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Criterias
    - ColourRedCriteria.php
    - RaceCarsCriteria.php
    - ...
    - Ship
    - Criterias
    - CreatedTodayCriteria.php
    - NotNullCriteria.php
    - ...

    Code Samples

    A Shared Criteria

    class OrderByCreationDateDescendingCriteria extends Criteria
    {
    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->orderBy('created_at', 'desc');
    }
    }

    Usage from Task

    public function run()
    {
    $this->userRepository->pushCriteria(new OrderByCreationDateDescendingCriteria());
    return $this->userRepository->paginate();
    }

    Criteria Accepting Data Input

    class ThisUserCriteria extends Criteria
    {
    private $userId;

    public function __construct($userId)
    {
    $this->userId = $userId;
    }

    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->where('user_id', '=', $this->userId);
    }
    }

    Passing Data from Task to Criteria

    public function run($user)
    {
    $this->accountRepository->pushCriteria(new ThisUserCriteria($user->id));
    return $this->accountRepository->paginate();
    }

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/events/index.html b/docs/10.x/optional-components/events/index.html index fceaad211..92952523e 100644 --- a/docs/10.x/optional-components/events/index.html +++ b/docs/10.x/optional-components/events/index.html @@ -4,13 +4,13 @@ Events | Apiato - +
    Version: 10.x

    Events

    Definition

    • Events provide a simple observer implementation, allowing you to subscribe and listen for various events that occur in your application.
    • Events are classes that can be fired from anywhere in your application.
    • An event class will usually be bound to one, or many Events Listeners Classes or has those Listeners registered to listen to it.
    • "fire" is the term that is usually used to call an Event.

    Principles

    • Events can be fired from Actions and or Tasks. It's preferable to choose one place only. (Tasks are recommended).
    • Events SHOULD be created inside the Containers. However, general Events CAN be created in the Ship layer.

    Rules

    • Event classes CAN be placed inside the Containers in Events folders or on the Ship for the general Events.
    • All Events MUST extend from App\Ship\Parents\Events\Event.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Events
    - SomethingHappenedEvent.php
    - ...
    - Listeners
    - ListenToMusicListener.php
    - ...

    - Ship
    - Events
    - GlobalStateChanged.php
    - SomethingBiiigHappenedEvent.php
    - ...

    Usage

    In Laravel, you can create and register events in multiple way. Read Laravel documentation to learn more about Events.

    Your custom EventServiceProvider needs to be registered in the containers MainServiceProvider as well.

    Broadcasting

    To define Broadcasting route go to app/Ship/Boardcasts/Routes.php.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/factories/index.html b/docs/10.x/optional-components/factories/index.html index 72970b553..63492f83d 100644 --- a/docs/10.x/optional-components/factories/index.html +++ b/docs/10.x/optional-components/factories/index.html @@ -4,13 +4,13 @@ Factories | Apiato - +
    Version: 10.x

    Factories

    Definition

    Factories (are a short name for Model Factories).

    Factories are used to generate some fake data with the help of Faker to be used for testing purposes.

    Factories are mainly used from Tests.

    Principles

    • Factories SHOULD be created in the Containers.

    Rules

    • All Factories MUST extend from App\Ship\Parents\Factories\Factory.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Factories
    - UserFactory.php
    - ...

    Code Samples

    A User Model Factory

    class UserFactory extends Factory
    {
    protected $model = User::class;

    public function definition(): array
    {
    static $password;

    return [
    'name' => $this->faker->name,
    'email' => $this->faker->unique()->safeEmail,
    'password' => $password ?: $password = Hash::make('testing-password'),
    'email_verified_at' => now(),
    'remember_token' => Str::random(10),
    'is_admin' => false,
    ];
    }
    }

    Usage from Tests or Anywhere Else

    // creating 4 users
    User::factory()->count(4)->create();
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/helpers/index.html b/docs/10.x/optional-components/helpers/index.html index b2b0d6fa5..2d0fb5574 100644 --- a/docs/10.x/optional-components/helpers/index.html +++ b/docs/10.x/optional-components/helpers/index.html @@ -4,13 +4,13 @@ Helpers | Apiato - +
    Version: 10.x

    Helpers

    Definition

    • Helpers are global PHP functions that you can call from anywhere in your application.
    • Helper files are simple PHP files that hold functions.

    Principles

    • Helpers SHOULD be created inside the Containers. However, general Helpers CAN be created in the Ship layer.
    • You can create as many helper files as you need, per container.
    • You can implement as many helper functions as you need, per helper file.
    • All Helper files will be autoloaded by the framework.

    Rules

    • Helpers CAN be placed inside the Containers in Helpers folder or on the Ship for the general Helpers.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Helpers
    - helpers.php
    - mix.php
    - ...

    - Ship
    - Helpers
    - helpers.php
    - mix.php
    - ...

    Usage

    if (!function_exists('add')) {
    function add(int $firstNumber, int $secondNumber): int
    {
    return $firstNumber + $secondNumber;
    }
    }
    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/jobs/index.html b/docs/10.x/optional-components/jobs/index.html index ca620db4b..af3dd668a 100644 --- a/docs/10.x/optional-components/jobs/index.html +++ b/docs/10.x/optional-components/jobs/index.html @@ -4,13 +4,13 @@ Jobs | Apiato - +
    Version: 10.x

    Jobs

    Definition

    • Jobs are simple classes that can do one thing or multiple related things.
    • Job is a name given to a class that is usually created to be queued (it's execution is usually deferred for later, after the execution of previous Jobs are completed).
    • Jobs can be scheduled to be executed later by a queuing mechanism (a queue system like beanstalkd).
    • When a Job class is dispatched, it performs its specific job and dies.
    • Laravel's queue worker will process every Job as it's pushed onto the queue.

    Principles

    • A Container MAY have more than one Job.

    Rules

    • All Jobs MUST extend from App\Ship\Parents\Jobs\Job.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Jobs
    - DoSomethingJob.php
    - DoSomethingElseJob.php

    Code Samples

    DemoJob

    class DemoJob extends Job
    {
    private $something;

    public function __construct(array $someData)
    {
    $this->something = $someData;
    }

    public function handle()
    {
    foreach ($this->something as $thing) {
    // do whatever you like
    }
    }
    }

    Check the parent Job class.

    Usage from Action

    // using helper function
    dispatch(new DemoJob($someData));

    // manually
    app(\Illuminate\Contracts\Bus\Dispatcher\Dispatcher::class)->dispatch(New DemoJob($someData));

    Execute Jobs Execution

    For running your Jobs checkout the Tasks Queuing page.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/languages/index.html b/docs/10.x/optional-components/languages/index.html index 3283499fc..7222e41b6 100644 --- a/docs/10.x/optional-components/languages/index.html +++ b/docs/10.x/optional-components/languages/index.html @@ -4,14 +4,14 @@ Languages | Apiato - +
    Version: 10.x

    Languages

    Definition

    Languages are not real Components, they are just files that holds translations.

    Rules

    • Languages CAN be placed inside the Containers. However, the default laravel resources/lang languages files are still loaded and can be used as well.

    • All Translations are namespaced as the camelCase of its Section name + @ + camelCase of its Container name.
      For example, translation key inside a translation file named messages inside MySection > MyContainer can be accessed like this: __(mySection@myContainer::messages.welcome)

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Resources
    - Languages
    - en
    - messages.php
    - users.php
    - ar
    - messages.php
    - users.php

    Usage

    Nothing much to show here, here's how you use translated strings:

    __('mySection@myContainer::messages.welcome');
    Further reading

    More info at Localization.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/mails/index.html b/docs/10.x/optional-components/mails/index.html index 708fd6619..b728ef517 100644 --- a/docs/10.x/optional-components/mails/index.html +++ b/docs/10.x/optional-components/mails/index.html @@ -4,13 +4,13 @@ Mails | Apiato - +
    Version: 10.x

    Mails

    Definition

    The Mail component allows you to describe an email and send it whenever needed.

    Principles

    • Containers MAY or MAY NOT have one or more Mail.

    • Ship may contain general Mails.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Mails\Mail.
    • Email Templates must be placed inside the Mail directory in a Templates directory app/Containers/{section}/{container}/Mails/Templates.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Mails
    - UserRegisteredMail.php
    - ...
    - Templates
    - user-registered.blade.php
    - ...
    - Ship
    - Mails
    - SomeMail.php
    - ...
    - Templates
    - some-template.blade.php
    - ...

    Code Samples

    A simple Mail

    class UserRegisteredMail extends Mail implements ShouldQueue
    {
    use Queueable;

    protected $user;

    public function __construct(User $user)
    {
    $this->user = $user;
    }

    public function build()
    {
    return $this->view('appSection@user::user-registered')
    ->to($this->user->email, $this->user->name)
    ->with([
    'name' => $this->user->name,
    ]);
    }
    }

    Usage from an Action

    Notifications can be sent from Actions or Tasks using the Mail Facade.

    Mail::send(new UserRegisteredMail($user));

    Email Templates

    Templates should be placed inside a folder Templates inside the Mail folder.

    To access a Mail template (same like accessing a web view) you must call the camelCase of its Section name + @ + camelCase of its Container name.

    In the example below we're using the user-registered.blade.php template in the AppSection Section > User Container.

    $this->view('appSection@user::user-registered');

    Configure Emails

    Open the .env file and set the from mail and address. This will be used globally whenever the from function is not called in the Mail.

    MAIL_FROM_ADDRESS=test@test.test
    MAIL_FROM_NAME="apiato"

    To use different email address in some classes add ->to($this->email, $this->name) to the build function in your Mail class.

    By default Apiato is configured to use Log Driver MAIL_DRIVER=log, you can change that from the .env file.

    Queueing A Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/middlewares/index.html b/docs/10.x/optional-components/middlewares/index.html index 57c3894ec..d480c7ec6 100644 --- a/docs/10.x/optional-components/middlewares/index.html +++ b/docs/10.x/optional-components/middlewares/index.html @@ -4,7 +4,7 @@ Middlewares | Apiato - + @@ -12,7 +12,7 @@
    Version: 10.x

    Middlewares

    Definition

    Middleware provide a convenient mechanism for filtering HTTP requests entering your application.

    You can enable and disable Middlewares as you wish.

    Principles

    • There are two types of Middlewares, General (applied on all the Routes by default) and Endpoints Middlewares (applied on some Endpoints).

    • The Middlewares CAN be placed in Ship layer or Container layer depending on its roles.

    Rules

    • If a Middleware is written inside a Container then it MUST be registered inside that Container.

    • To register Middlewares in a Container the container needs to have a MiddlewareServiceProvider, and like all other Container Providers it MUST be registered in the MainServiceProvider of that Container.

    • General Middlewares SHOULD live in the Ship layer app/Ship/Middlewares/* and are registered in the app/Ship/Kernels/HttpKernel.

    • Third Party packages Middleware CAN be registered in Containers or on the Ship layer (wherever they make more sense). For example the jwt.auth middleware "provided by the JWT package" should be registered in the Authentication Container (Containers/AppSection/Authentication/Providers/MiddlewareServiceProvider.php).

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Middlewares
    - WebAuthentication.php
    - Ship
    - Middleware
    - Http
    - EncryptCookies.php
    - VerifyCsrfToken.php

    Code Sample

    Middleware Registration Inside the Container Example

    class MiddlewareServiceProvider extends MiddlewareProvider
    {
    protected array $middlewares = [
    // ..
    ];

    protected array $middlewareGroups = [
    'web' => [
    // ..
    ],
    'api' => [
    // ..
    ],
    ];

    protected array $routeMiddleware = [
    // apiato User Authentication middleware for Web Pages
    'guest' => RedirectIfAuthenticated::class
    ];
    }

    Middleware Registration Inside the Ship Layer (HTTP Kernel)

    class HttpKernel extends LaravelHttpKernel
    {
    /**
    * The application's global HTTP middleware stack.
    *
    * These middleware are run during every request to your application.
    *
    * @var array
    */
    protected $middleware = [
    // Laravel middleware's
    // \App\Http\Middleware\TrustHosts::class,
    TrustProxies::class,
    HandleCors::class,
    PreventRequestsDuringMaintenance::class,
    ValidatePostSize::class,
    TrimStrings::class,
    ConvertEmptyStringsToNull::class,
    ];

    /**
    * The application's route middleware groups.
    *
    * @var array
    */
    protected $middlewareGroups = [
    'web' => [
    EncryptCookies::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    // \Illuminate\Session\Middleware\AuthenticateSession::class,
    ShareErrorsFromSession::class,
    VerifyCsrfToken::class,
    SubstituteBindings::class,
    ],

    'api' => [
    // Note: The "throttle" Middleware is registered by the RoutesLoaderTrait in the Core
    SubstituteBindings::class,
    ValidateJsonContent::class,
    ProcessETagHeadersMiddleware::class,
    ProfilerMiddleware::class,
    ],
    ];

    /**
    * The application's route middleware.
    *
    * These middleware may be assigned to groups or used individually.
    *
    * @var array
    */
    protected $routeMiddleware = [
    'auth' => Authenticate::class,
    // 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => SetCacheHeaders::class,
    // Note: The "can" Middleware is registered by MiddlewareServiceProvider in Authorization Container
    // 'can' => \Illuminate\Auth\Middleware\Authorize::class,
    // Note: The "guest" Middleware is registered by MiddlewareServiceProvider in Authentication Container
    // 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => RequirePassword::class,
    'signed' => ValidateSignature::class,
    'throttle' => ThrottleRequests::class,
    'verified' => EnsureEmailIsVerified::class,
    ];

    /**
    * The priority-sorted list of middleware.
    *
    * Forces non-global middleware to always be in the given order.
    *
    * @var string[]
    */
    protected $middlewarePriority = [
    EncryptCookies::class,
    StartSession::class,
    ShareErrorsFromSession::class,
    Authenticate::class,
    ThrottleRequests::class,
    AuthenticateSession::class,
    SubstituteBindings::class,
    Authorize::class,
    ];
    }
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/migrations/index.html b/docs/10.x/optional-components/migrations/index.html index 410d6d993..be4b0e419 100644 --- a/docs/10.x/optional-components/migrations/index.html +++ b/docs/10.x/optional-components/migrations/index.html @@ -4,13 +4,13 @@ Migrations | Apiato - +
    Version: 10.x

    Migrations

    Definition

    Migrations (are the short name for Database Migrations).

    Migrations are the version control of your database. They are very useful for generating and documenting the database tables.

    Principles

    • Migrations SHOULD be created inside the Containers folders.

    • Migrations will be autoloaded by the framework.

    Rules

    • No need to publish the DB Migrations. Just run the artisan migrate command and Laravel will read the Migrations from the Containers.

    Folder Structure

       - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Migrations
    - 2200_01_01_000001_create_something_table.php
    - ...

    Code Samples

    User CreateDemoTable Migrations

    class CreateDemoTable extends Migration
    {
    public function up()
    {
    Schema::create('demos', function (Blueprint $table) {
    $table->increments('id');
    // ...
    $table->timestamps();
    $table->softDeletes();
    });
    }

    public function down()
    {
    Schema::drop('demos');
    }
    }

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/notifications/index.html b/docs/10.x/optional-components/notifications/index.html index 5af1dcc62..2c1cad29d 100644 --- a/docs/10.x/optional-components/notifications/index.html +++ b/docs/10.x/optional-components/notifications/index.html @@ -4,7 +4,7 @@ Notifications | Apiato - + @@ -12,7 +12,7 @@
    Version: 10.x

    Notifications

    Definition

    Notifications allow you to inform the user about a state changes in your application.

    The Laravel notifications supports sending notifications across a variety of channels (mail, SMS, Slack, Database...).

    When using the Database channel, the notifications will be stored in a database to be displayed in your client interface.

    Principles

    • Containers MAY or MAY NOT have one or more Notification.

    • Ship MAY contain Application general Notifications.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Notifications\Notification.

    Folder Structure

    - app
    - Containers
    - {select-name}
    - {container-name}
    - Notifications
    - UserRegisteredNotification.php
    - ...
    - Ship
    - Notifications
    - SystemFailureNotification.php
    - ...

    Code Samples

    A Simple Notification

    class BirthdayReminderNotification extends Notification implements ShouldQueue
    {
    use Queueable;

    protected $notificationMessage;

    public function __construct($notificationMessage)
    {
    $this->notificationMessage = $notificationMessage;
    }

    public function toArray($notifiable)
    {
    return [
    'content' => $this->notificationMessage,
    ];
    }

    public function toMail($notifiable)
    {
    // $notifiable is the object you want to notify "e.g. user"
    return (new MailMessage)
    ->subject("Hello World")
    ->line("Hi, $notifiable->name")
    ->line($this->notificationMessage);
    }

    public function toSms($notifiable)
    {
    // ...
    }

    // ...
    }

    Usage from an Action or Task

    Notifications can be sent from Actions or Tasks using the Notification Facade.

    \Notification::send($user, new BirthdayReminderNotification($notificationMessage));

    Alternatively you can use the Illuminate\Notifications\Notifiable trait on the notifiable object "e.g. User" and then call it as follows:

    // call notify, found on the Notifiable trait
    $user->notify(new BirthdayReminderNotification($notificationMessage));

    Select Channels

    To select a notification channel, apiato have the app/Ship/Configs/notification.php config file where you can define the array of supported channels "e.g. SMS, Email, WebPush...", to be used for all your notifications.

    If you want to override the configuration for some notifications classes, or if you prefer to define the channels within each notification class itself, you can override the via function public function via($notifiable) in the notification class and define your channels.

    Checkout laravel notification channels for list of supported integrations.

    Queueing a Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    Use DB channel

    Generally you need to generate the notification migration php artisan notifications:table, then run php artisan migrate, however just running the migration command will do the job, since Apiato already adds the _create_notifications_table.php in the default migrations files directory app/Ship/Migrations/.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/providers/index.html b/docs/10.x/optional-components/providers/index.html index b640fab35..dabdff998 100644 --- a/docs/10.x/optional-components/providers/index.html +++ b/docs/10.x/optional-components/providers/index.html @@ -4,7 +4,7 @@ Providers | Apiato - + @@ -14,7 +14,7 @@ In apiato those providers have been renamed and moved to the Ship Layer app/Ship/Parents/Providers/*:

    • AppServiceProvider
    • RouteServiceProvider
    • AuthServiceProvider
    • BroadcastServiceProvider
    • EventsServiceProvider
    note

    You should not touch those providers, instead you have to extend them from a containers providers in order to modify them. Example: the app/Containers/AppSection/Authentication/Providers/AuthProvider.php is extending the AuthServiceProvider to modify it.

    Those providers are not auto registered by default, thus writing any code there will not be available, unless you extend them. Once extended the child Provider should be registered in its Container Main Provider, which makes its parent available.

    This rule does not apply to the RouteServiceProvider since it's required by Apiato, this Provider is registered by the ShipProvider.

    Check How Service Providers are auto-loaded.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/repositories/index.html b/docs/10.x/optional-components/repositories/index.html index 5ce58482a..b9e625d5e 100644 --- a/docs/10.x/optional-components/repositories/index.html +++ b/docs/10.x/optional-components/repositories/index.html @@ -4,13 +4,13 @@ Repositories | Apiato - +
    Version: 10.x

    Repositories

    Definition

    The Repository classes are an implementation of the Repository Design Pattern.

    Their major roles are separating the business logic from the data (or the data access Task).

    Repositories save and retrieves Models to/from the underlying storage mechanism.

    The Repository is used to separate the logic that retrieves the data and maps it to a Model, from the business logic that acts on the Model.

    Principles

    • Every Model SHOULD have a Repository.

    • A Model SHOULD always get accessed through its Repository. (Never accessed directly).

    Rules

    • All Repositories MUST extend from App\Ship\Parents\Repositories\Repository. Extending from this class will give you access to methods like (find, create, update and much more).

    • Repository name should be same as it's model name (model: Foo -> repository: FooRepository).

    • If a Repository belongs to a Model whose name is not equal to its Container name, then the Repository implement model() method like this.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Repositories
    - UserRepository.php
    - ...

    Code Samples

    Demo Repository

    class DemoRepository extends Repository
    {
    protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];
    }

    Usage

    // paginate the data by 10
    $users = $userRepository->paginate(10);

    // search by 1 field
    $cars = $carRepository->findByField('colour', $colour);

    // searching multiple fields
    $offer = $offerRepository->findWhere([
    'offer_id' => $offer_id,
    'user_id' => $user_id,
    ])->first();

    //....

    Different Model and Container Name

    The model() method must be implemented when the model has different name than the container.

    class DemoRepository extends Repository
    {
    // ...

    public function model(): string
    {
    return Demo::class;
    }
    }

    Other Properties:

    API Query Parameters Property

    To enable query parameters (?search=text,...) in your API you need to set the property $fieldSearchable on the Repository class, to instruct the querying on your model. More details.

        protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];

    All other Properties

    Apiato uses the l5-repository package, to provide a lot of powerful features to the repository class.

    Further reading

    To learn more about all the properties you can use, visit the andersao/l5-repository package documentation.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/seeders/index.html b/docs/10.x/optional-components/seeders/index.html index eba6e1537..a63535990 100644 --- a/docs/10.x/optional-components/seeders/index.html +++ b/docs/10.x/optional-components/seeders/index.html @@ -4,13 +4,13 @@ Seeders | Apiato - +
    Version: 10.x

    Seeders

    Definition

    Seeders (are a short name for Database Seeders).

    Seeders are classes made to seed the database with real data, this data usually should exist in the Application after the installation (Example: the default Users Roles and Permissions or the list of Countries).

    Principles

    • Seeders SHOULD be created in the Containers. (If the container is using a package that publishes a Seeder class, this class should be manually placed in the Container that make use of it. Do not rely on the package to place it in its right location).

    Rules

    • Seeders should be in the right directory inside the container to be loaded.

    • To avoid any conflict between containers seeders classes, you SHOULD always prepend the Seeders of each container with the container name. (Example: UserPermissionsSeeder, ItemPermissionsSeeder).

      note

      If 2 seeders classes have the same name but live in different containers, one of them will not be loaded. In these situations you can also prepend the seeder name with the section name

    • If you wish to order the seeding of the classes, you can just append _1, _2 to your classes.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Seeders
    - ContainerNameRolesSeeder_1.php
    - ContainerNamePermissionsSeeder_2.php
    - ...

    Code Samples

    Demo Seeder

    class DemoSeeder_1 extends Seeder
    {
    public function run()
    {
    app(CreateRoleTask::class)->run('admin', 'Administrator', 'Administrator Role', 999);
    // ...
    }
    }
    note

    Same Seeder class is allowed to contain seeding for multiple Models.

    Run the Seeders

    After registering the Seeders you can run this command:

    php artisan db:seed

    Migrate & seed at the same time

    php artisan migrate --seed

    Testing Seeder Command

    It's useful sometimes to create a big set of testing data. apiato facilitates this task:

    1. Open app/Ship/Seeders/SeedTestingData.php and write your testing data here.
    2. Run this command any time you want this data available (example at staging servers):
    php artisan apiato:seed-test
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/tests/index.html b/docs/10.x/optional-components/tests/index.html index e892fa867..3b8eaf2ab 100644 --- a/docs/10.x/optional-components/tests/index.html +++ b/docs/10.x/optional-components/tests/index.html @@ -4,13 +4,13 @@ Tests | Apiato - +
    Version: 10.x

    Tests

    Definition

    Tests classes are created to test if the Application classes are working as expected.

    The two most essential Test types for this architecture are the Unit Tests and the Functional Tests. However, Integration and Acceptance Tests can be used as well.

    Principles

    • Containers MAY be covered by all types of Tests.

    • Use Functional Tests to test Container Routes are doing what's expected from them.

    • Use Unit Tests to test Container Actions and Tasks are doing what's expected from them.

    Rules

    • All Container Test classes SHOULD extend from a Container Internal TestCase class {container-name}/Tests/TestCase.php. The container TestCase MUST extend main TestCase on Ship layer App\Ship\Parents\Tests\PhpUnit\TestCase. (Adding functions to the container TestCase allows sharing those functions between all Test classes of the Container).

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Tests
    - TestCase.php // the container test case
    - Unit
    - CreateUserTest.php
    - UpdateUserTest.php
    - ...
    - UI
    - API
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - WEB
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - CLI
    - Tests
    - Functional
    - BackupDataTest.php
    - ...

    Code Sample

    class DeleteUserTest extends TestCase
    {
    protected $endpoint = 'delete@v1/users/{id}';

    protected array $access = [
    'roles' => '',
    'permissions' => 'delete-users',
    ];

    public function testDeleteExistingUser()
    {
    $user = $this->getTestingUser();

    $response = $this->injectId($user->id)->makeCall();

    $response->assertStatus(204);
    }
    }

    See the Tests Helpers Page

    - + \ No newline at end of file diff --git a/docs/10.x/optional-components/values/index.html b/docs/10.x/optional-components/values/index.html index a39e72e11..5f5e40548 100644 --- a/docs/10.x/optional-components/values/index.html +++ b/docs/10.x/optional-components/values/index.html @@ -4,7 +4,7 @@ Values | Apiato - + @@ -12,7 +12,7 @@
    Version: 10.x

    Values

    Definition & Principles

    Values are short names for the known "Value Objects" which are simple Objects, pretty similar to Models in the concept of representing data, but they do not get stored in the DB, thus they don't have ID's. They also do not hold functionality or change any state, they just hold data.

    A Value Object is an immutable object that is defined by its encapsulated attributes. We create Value Object when we need it to represent/serve/manipulate some data (attached as attributes), and we'll kill it later when we don't need it anymore, to recreate it again when needed.

    Rules

    • All Models MUST extend from App\Ship\Parents\Values\Value.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Values
    - Output.php
    - Region.php
    - ...

    Code Sample

    class Location extends Value
    {
    private $x = null;
    private $y = null;
    protected $resourceKey = 'locales';

    public function __construct($x, $y)
    {
    $this->x = $x;
    $this->y = $y;
    }

    public function getCoordinatesAsString()
    {
    return $this->x . ' - ' . $this->y;
    }
    }
    - + \ No newline at end of file diff --git a/docs/10.x/upgrade-guide/index.html b/docs/10.x/upgrade-guide/index.html index ef1471c47..c66fa27e5 100644 --- a/docs/10.x/upgrade-guide/index.html +++ b/docs/10.x/upgrade-guide/index.html @@ -4,7 +4,7 @@ Upgrade Guide | Apiato - + @@ -23,7 +23,7 @@ and the term New Project (referring to the new freshly installed Apiato 5.0).

    1) Download and install Apiato 5.0. See Application Setup.

    2) Delete the Containers directory app/Containers from the new project.

    3) Move the Containers directory app/Containers from the old project to the new project.

    4) Open this file app/Ship/composer.json in your old project and only copy the required dependencies, from the old project to the same file in the new project.

    5) Again, open the app/Ship/composer.json file in the new project, and remove the following dependencies: guzzlehttp/guzzle, prettus/l5-repository, barryvdh/laravel-cors, spatie/laravel-fractal, vinkla/hashids and johannesschobel/apiato-container-installer.

    6) Move and replace the following directories from the old project to the new project: config, public, resources, database and storage.

    7) Open config/app.php and replace App\Ship\Engine\Providers\PortoServiceProvider::class with Apiato\Core\Providers\ApiatoProvider::class.

    8) Move .gitignore, phpunit.xml and .env files, from the old project to the new project.

    9) Open the .env file on the new project and append this to it API_RATE_LIMIT_ENABLED=true.

    10) Open phpunit.xml file of the new project and delete this line from the file <file>./app/Ship/Engine/Loaders/FactoryMixer/FactoriesLoader.php</file>.

    11) If you had live testing data in your old project inside app/Ship/Seeders/Data/Testing/Seeders/TestingDataSeeder.php file, then copy that file content and past it in the new project inside app/Ship/Seeders/SeedTestingData.php. You will need to rename the class (not the file) from TestingDataSeeder to SeedTestingData, and you will need to update the namespace from namespace App\Ship\Seeders\Data\Testing\Seeders; to namespace App\Ship\Seeders;.

    12) If you ever used the HashIdTrait, you need to search and replace this namespace App\Ship\Engine\Traits\HashIdTrait with this Apiato\Core\Traits\HashIdTrait.

    13) Run composer update. If you got any error at this step, try to solve it or open an Issue.

    14) Move the .git directory from the old project to the new one. Add all changes git add . then commit git commit -m 'upgrade Apiato from 4.1 to 5.0'.

    15) Run your tests vendor/bin/phpunit.

    That's it :)

    How to manually upgrade older versions to 4.1?

    Use the Manual Upgrading Guide below.

    Manual Upgrading Guide

    These commands and examples, are compatible with the Apiato 8.0 upgrade. You can just copy/past.

    1) Checkout a new branch from your stable branch, to perform the upgrade.

    git checkout -b upgrade-apiato

    2) Configure a new remote (upstream) that points to the official Apiato repository.

    git remote add upstream https://github.com/apiato/apiato

    Verify the new upstream repository was added, by listing the current configured remote repositories.

    git remote -vv

    origin git@bitbucket.org:username/my-awesome-api.git (fetch)
    origin git@bitbucket.org:username/my-awesome-api.git (push)
    upstream git@github.com:apiato/apiato.git (fetch)
    upstream git@github.com:apiato/apiato.git (push)

    3) Checkout a new branch to hold the latest Apiato changes. This branch will be merged into your upgrade-apiato branch created above.

    git checkout -b apiato-{version}
    // Example: git checkout -b apiato-8.0

    4) Configure this branch to track an upstream specific branch.

    Replace {upstream-branch-name} with the branch name you want to upgrade to (for example 8.0).

    git fetch upstream {upstream-branch-name}
    // Example: git fetch upstream 8.0

    git branch --set-upstream-to upstream/{upstream-branch-name}
    // Example: git branch --set-upstream-to upstream/8.0

    Verify your local branch is tracking the Apiato specified upstream branch.

    git branch -vv

    apiato 77b4d945 [upstream/{upstream-branch-name}] ...
    master 77d302aa [origin/master] ...

    5) Make this branch identical to the remote upstream branch

    git reset --hard upstream/{upstream-branch-name}
    // Example: git reset --hard upstream/8.0

    Verify this branch now contains the latest changes from the upstream branch.

    git log

    6) Switch back to the upgrade-apiato branch

    git checkout upgrade-apiato

    7) Now lets merge the 2 branches. This step can be done in two ways:

    Option A: Merge all the changes together and solve the conflicts if any. (Recommended)

    You can execute the next command with different different parameters, below are 2 options to pick whatever feels safer to you. Do not execute both of them.

    A1: This will overwrite your changes with the upstream changes. (Try this first and if your tests failed then you can try the second one).

    git merge --allow-unrelated-histories --strategy-option=theirs apiato-{version}
    // Example: git merge --allow-unrelated-histories --strategy-option=theirs apiato-8.0

    A2: This will let you solve all the conflicts manually. (Can be the most secure choice, but it's time consuming as well.)

    git merge --allow-unrelated-histories apiato-{version}
    // Example: git merge --allow-unrelated-histories apiato-8.0

    Option B: Manually cherry pick the commits you likes to have:

    git log {upstream-branch-name}

    (copy each commit ID, one by one)

    git cherry-pick {commit-ID}

    (if you get any conflict solve it and keep moving)

    8) Compare your .env with the new .env-example and update it.

    9) Check everything is working. By running Composer install first then re-running your tests.

    • Read the changelog releases page.
    • You may want to update your custom containers dependencies, simply follow the composer install error outputs and bump each failing dependency. (Hint: visit each package releases page, and use the version which supports the supported version of Laravel).
    • You may need to fix the failing tests.
    composer install  &&  vendor/bin/phpunit

    10) Finally, merge the upgrade-apiato (which contains the upgraded changes) with your stable branch (could be master).

    git checkout master
    git merge upgrade-apiato

    php artisan -V

    Enjoy :)

    - + \ No newline at end of file diff --git a/docs/11.x/additional-features/documentation/index.html b/docs/11.x/additional-features/documentation/index.html index 7a19999af..b77468914 100644 --- a/docs/11.x/additional-features/documentation/index.html +++ b/docs/11.x/additional-features/documentation/index.html @@ -4,7 +4,7 @@ Documentation | Apiato - + @@ -15,7 +15,7 @@ access-private-docs-permission values in documentation config. By default, users need access-private-docs permission to access private docs.

    Edit Default Generated Values in Templates

    Apiato by defaults generates 2 API documentations, each one has its own apidoc.json file. Both can be modified from the Documentation Container in Containers/Vendor/Documentation/ApiDocJs/ and need Source code modification.

    Edit the Documentation Header

    The header is usually the Overview of your API. It contains Info about authenticating users, making requests, responses, potential errors, rate limiting, pagination, query parameters and anything you want.

    All this information is written in app/Containers/Vendor/Documentation/ApiDocJs/shared/header.template.md file, and the same file is used as header for both private and public documentations.

    To edit its content you need to modify its source code and open the markdown file in any markdown editor and edit it.

    You will notice some variables like {{rate-limit}} and {{token-expires}}. Those are replaced when running apiato:apidoc with real values from your application configuration files.

    Feel free to extend them to include more info about your API from the app/Containers/Vendor/Documentation/Tasks/RenderTemplatesTask.php class.

    Localization for Documentation Header

    Default, the documentation title is in English en localization.

    See which locales are supported by going in app/Containers/Vendor/Documentation/ApiDocJs/shared/

    There will be some header.template.{locale}.md files in the folder.

    You can change the language by adding APIDOC_LOCALE=ru to the .env file.

    If you didn't find a file with your locale, you can create it. You need to modify its source code and create new file like header.template.cn.md

    - + \ No newline at end of file diff --git a/docs/11.x/additional-features/localization/index.html b/docs/11.x/additional-features/localization/index.html index 9a7ca067a..b54cfb4b3 100644 --- a/docs/11.x/additional-features/localization/index.html +++ b/docs/11.x/additional-features/localization/index.html @@ -4,7 +4,7 @@ Localization | Apiato - + @@ -37,7 +37,7 @@ language in this specific language (e.g., locale_name => Deutsch). Furthermore, the language name is outputted in the applications default name (e.g., configured in app.locale). This would result in default_name => German.

    The same applies to the regions that are defined (e.g., de-DE). Consequently, this results in locale_name => Deutschland and default_name = Germany.

    Tests

    To change the default language in your tests requests. You can set the env language in the phpunit.xml file.

    - + \ No newline at end of file diff --git a/docs/11.x/additional-features/overview/index.html b/docs/11.x/additional-features/overview/index.html index f6142de4d..d57b07a86 100644 --- a/docs/11.x/additional-features/overview/index.html +++ b/docs/11.x/additional-features/overview/index.html @@ -4,14 +4,14 @@ Overview | Apiato - +
    Version: 11.x

    Overview

    Apiato ships with a few pre-defined and pre-configured containers. Some of these containers (e.g. Documentation) are installed by default with a fresh Apiato project.

    - + \ No newline at end of file diff --git a/docs/11.x/additional-features/social-authentication/index.html b/docs/11.x/additional-features/social-authentication/index.html index bcf52503c..dda9784bf 100644 --- a/docs/11.x/additional-features/social-authentication/index.html +++ b/docs/11.x/additional-features/social-authentication/index.html @@ -4,7 +4,7 @@ Social Authentication | Apiato - + @@ -18,7 +18,7 @@ visit app/Containers/Vendor/Socialauth/UI/API/Routes/AuthenticateAll.v1.private.php.

  • The endpoint above should return the User and his Personal Access Token.

  • Example Google Response:

    {
    "data": {
    // user data
    .
    .
    .
    // additional social data if you have updated your transformer as mentioned above
    "social_auth_provider": "google",
    "social_id": "113834952367767922133",
    "social_avatar": {
    "avatar": "https:\/\/lh6.googleusercontent.com\/-OSItz6IHbSw\/AAA\/AMZuucltEs\/s96-c\/photo.jpg",
    "original": "https:\/\/lh6.googleusercontent.com\/-OSItz6IHbSw\/AAA\/AMZuucltEs\/s96-c\/photo.jpg"
    }
    },
    "meta": {
    "include": [
    "roles"
    ],
    "custom": {
    "token_type": "personal",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...."
    }
    }
    }
    tip

    For testing purposes Apiato provides two web endpoints

    1. http://apiato.test/auth/{provider}/redirect which act as a client (step 3 above)
    2. http://apiato.test/auth/{provider}/callback which you can use in your provider's developer dashboard for callback url.
      Use those endpoints from your browser (replace the provider with any of the supported providers facebook, twitter,...) to get the oauth info and user data respectively.

    Social Authentication Container Customization

    You can customize this container by publishing its config and modifying its values

    php artisan vendor:publish

    Config file will be copied to app/Ship/Configs/vendor-socialAuth.php

    Support new Auth Provider

    1. Publish the configs
    2. Create your new auth provider by implementing App\Containers\Vendor\SocialAuth\Contracts\SocialAuthProvider interface.
      To get an idea about how to implement your own provider you can check out supported providers here App\Containers\Vendor\SocialAuth\SocialAuthProviders.
    3. Add your new provider to providers array in the vendor-socialAuth config.
        'providers' => [
    ...
    'something' => Location\Of\Your\Provider\SomthingSocialAuthProvider::class,
    ],

    Changing default used Repository, Transformer & DB user table name

    This container depends on Apiato's default user repository, transformer & database user table name. If you changed those defaults you can update and provide them in the configs.

    - + \ No newline at end of file diff --git a/docs/11.x/contribution-guide/index.html b/docs/11.x/contribution-guide/index.html index 7bec2c86b..8f21c4927 100644 --- a/docs/11.x/contribution-guide/index.html +++ b/docs/11.x/contribution-guide/index.html @@ -4,7 +4,7 @@ Contribution Guide | Apiato - + @@ -42,7 +42,7 @@ 3 - Make sure you write a complete Changelog, in the release description. 4 - Change the default branch on GitHub to that new branch. 5 - If you updated the documentation, and you should! then visit the documentation repository and merge the PR into master.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/api-versioning/index.html b/docs/11.x/core-features/api-versioning/index.html index 3a707757d..dc1edbf5a 100644 --- a/docs/11.x/core-features/api-versioning/index.html +++ b/docs/11.x/core-features/api-versioning/index.html @@ -4,13 +4,13 @@ API Versioning | Apiato - +
    Version: 11.x

    API Versioning

    Since Laravel does not support API versioning, Apiato provide a very easy way to implement versioning for your API.

    How it works

    Create:

    When creating a new API endpoint, specify the version number in the route file name following this naming format {endpoint-name}.{version-number}.{documentation-name}.php.

    Example:

    • MakeOrder.v1.public.php
    • MakeOrder.v2.public.php
    • ListOrders.v1.private.php

    Use:

    Automatically the endpoint inside that route file will be accessible by adding the version number to the URL.

    Example:

    • http://api.apiato.test/v1/register
    • http://api.apiato.test/v1/orders
    • http://api.apiato.test/v2/stores/123

    Version the API in header instead of URL

    First remove the URL version prefix:

    1. Edit app/Ship/Configs/apiato.php, set prefix to 'enable_version_prefix' => 'false',.
    2. Implement the Header versioning anyway you prefer. (this is not implemented in Apiato yet. Consider a contribution).
    - + \ No newline at end of file diff --git a/docs/11.x/core-features/authentication/index.html b/docs/11.x/core-features/authentication/index.html index 1f7636fff..8cda5c435 100644 --- a/docs/11.x/core-features/authentication/index.html +++ b/docs/11.x/core-features/authentication/index.html @@ -4,7 +4,7 @@ Authentication | Apiato - + @@ -70,7 +70,7 @@ http://myapp.com/reset-password?email=mohammad.alavi1990@gmail.com&token=51f8d80182f3785648c9b9dc7162719d158fc418b3cca86c14963638ec83d663

    3) And when user click on that link it will go to your front end app reset password page. And then from there you should get the user's new password and call the /password-reset endpoint with all the required fields to reset the password.

    note

    You must set up the email to get this function to work, however for testing purposes set the MAIL_MAILER=log in your .env file in order to the see the email content in the log file storage/logs/laravel.log.

    Social Authentication

    For Social Authentication visit the Social Authentication page.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/authorization/index.html b/docs/11.x/core-features/authorization/index.html index e8a03fcef..34a2cca6c 100644 --- a/docs/11.x/core-features/authorization/index.html +++ b/docs/11.x/core-features/authorization/index.html @@ -4,13 +4,13 @@ Authorization | Apiato - +
    Version: 11.x

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Behind the scenes apiato is using the Laravel's authorization functionality that was introduced in version 5.1.11 with the helper package laravel-permission. So you can always refer to the correspond documentation for more information.

    How it works

    Authorization in apiato is very simple and easy.

    1) Create some Roles and permissions. By default, an admin role and some permissions are provided by Apiato. You can find the code in app/Containers/AppSection/Authorization/Data/Seeders/* directory.

    2) Attach some permissions to the roles.

    3) Now start creating users (or use existing users), to assign them to the new created Roles.

    4) Finally, you need to protect your endpoints by Permissions (or/and Roles). The right place to do that is the Requests class.

    Example protecting the (delete user) endpoint with delete-users permission:

    class DeleteUserRequest extends Request
    {
    protected array $access = [
    'permissions' => 'delete-users',
    'roles' => '',
    ];

    public function authorize(): bool
    {
    return $this->check([
    'hasAccess',
    ]);
    }
    }

    For detailed explanation of this example, please visit the Requests Page.

    Responses

    Authorization failed JSON response:

    {
    "message": "This action is unauthorized."
    }

    Assign Roles & Permission to the Testing User

    You will need to set $access property in your test class, check out the Tests Helpers page for more details.

    Seeding some users (Admins)

    By default, Apiato comes with a Super Admin.

    This Super Admin Credentials are:

    This Admin seeded by app/Containers/Authorization/Data/Seeders/AuthorizationDefaultUsersSeeder_3.php.

    The Default Super User, has a default role admin.

    The admin default role has no permissions given to it.

    To give permissions to the admin role (or any other role), you can use the dedicated endpoints (from your custom Admin Interface).

    Checkout each container Seeders directory app/Containers/AppSection/{container-name}/Data/Seeders/, to edit the default Users, Roles and Permissions.

    Roles & Permissions guards

    By default, Apiato uses a single guard called web for all it's roles and permissions, you can add/edit this behavior and support multiple guards at any time. Refer to the laravel-permission package for more details.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/code-generator/index.html b/docs/11.x/core-features/code-generator/index.html index 5d3c216a0..fb3707c88 100644 --- a/docs/11.x/core-features/code-generator/index.html +++ b/docs/11.x/core-features/code-generator/index.html @@ -4,7 +4,7 @@ Code Generator | Apiato - + @@ -17,7 +17,7 @@ the file and the folder structure needs to be the same as in vendor/apiato/core/Generator/Stubs.

    Say, if you like to change the action -> create.stub, simply copy the file to app/Ship/Generators/CustomStubs/actions/create.stub and start adapting it to your needs.

    If you run the respective command (e.g., in this case php artisan apiato:generate:action) and choose Create type this would read your specific create.stub file instead of the pre-defined one!

    Contributing

    If you would like to add your own generators, please check out the Contribution Guide.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/data-caching/index.html b/docs/11.x/core-features/data-caching/index.html index 1f9ce96b0..f4138a839 100644 --- a/docs/11.x/core-features/data-caching/index.html +++ b/docs/11.x/core-features/data-caching/index.html @@ -4,13 +4,13 @@ Data Caching | Apiato - +
    Version: 11.x

    Data Caching

    Enable / Disable Eloquent Query Caching

    info

    This feature is disabled By default.

    To enable it, go to app/Ship/Configs/repository.php config file and set cache > enabled => true, or set it from the .env file using ELOQUENT_QUERY_CACHE.

    More details can be found here.

    Users can skip the query caching and request new data by passing specific parameter to the Endpoint. Checkout its documentation here.

    Change different caching settings

    You can use different cache setting for each repository.

    To set cache settings on each repository, first the caching must be enabled, second you need to set some properties on the repository class to override the default values.

    For more details about all the properties refer to the L5 repository package documentation.

    Note: you don't need to use the CacheableRepository trait or implement the CacheableInterface since they both exist on the Abstract repository class (App\Ship\Parents\Repositories\Repository).

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/default-endpoints/index.html b/docs/11.x/core-features/default-endpoints/index.html index 5e5740454..5800aa3dd 100644 --- a/docs/11.x/core-features/default-endpoints/index.html +++ b/docs/11.x/core-features/default-endpoints/index.html @@ -4,13 +4,13 @@ Default Endpoints | Apiato - +
    Version: 11.x

    Default Endpoints

    Apiato comes loaded with many useful API endpoints for speeding up the development process.

    You can see the endpoints in three ways:

    • In Terminal, by running php artisan route:list -c.
    • In Browser, by generating the beautiful detailed documentation. See API Docs Generator.
    • In Code, by navigating to the Routes folder of each container's UI.
    - + \ No newline at end of file diff --git a/docs/11.x/core-features/etag/index.html b/docs/11.x/core-features/etag/index.html index c38933011..f92c79468 100644 --- a/docs/11.x/core-features/etag/index.html +++ b/docs/11.x/core-features/etag/index.html @@ -4,7 +4,7 @@ ETag | Apiato - + @@ -12,7 +12,7 @@
    Version: 11.x

    ETag

    ETag Middleware

    Apiato provides an ETag Middleware (ProcessETagHeadersMiddleware) that implements the Shallow technique. It can be used to reduce bandwidth on the client side (especially for Mobile devices).

    Enable / Disable ETag

    This feature is disabled By default. To enable it go to app/Ship/Configs/apiato.php and set use-etag to true. Of course your client should send the If-None-Match HTTP Header (= etag) in his request for this feature to work properly.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/hash-id/index.html b/docs/11.x/core-features/hash-id/index.html index 59e7ee108..4ccf744b7 100644 --- a/docs/11.x/core-features/hash-id/index.html +++ b/docs/11.x/core-features/hash-id/index.html @@ -4,13 +4,13 @@ Hash ID | Apiato - +
    Version: 11.x

    Hash ID

    Hashing your internal ID's, is a very helpful feature for security reasons (to prevent some hack attacks) and business reasons (to hide the real total records from your competitors).

    Enable Hash ID

    Set the HASH_ID=true in the .env file.

    Also, with the feature make sure to always use the getHashedKey() on any model, whenever you need to return an ID (mainly from transformers) weather hashed ID or not.

    Example:


    'id' => $user->getHashedKey(),

    Note: if the feature is set to false HASH_ID=false the getHashedKey() will return the normal ID.

    Usage

    There are 2 ways an ID's can be passed to your system via the API:

    In URL example: www.apiato.test/items/abcdef.

    In parameters example: [GET] or [POST] www.apiato.test/items?id=abcdef.

    in both cases you will need to inform your API about what's coming form the Request class.

    Checkout the Requests page. After setting the $decode and $urlParameters properties on your Request class, the ID will be automatically decoded for you to apply validation rules on it or/and use it from your controller. ($request->id will return the decoded ID)

    Configuration

    You can change the default length and characters used in the ID from the config file app/Ship/Configs/hashids.phpor in the .env file by editing the HASH_ID_LENGTH value.

    You can set the HASH_ID_KEY in the .env file to any random string. You can generate this from any of the online random string generators, or run head /dev/urandom | tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_{|}~' | head -c 32 ; echo on the linux command-line. Apiato defaults to the APP_KEY should this not be set.

    The HASH_ID_KEY acts as the salt during hashing of the ID. This should never be changed in production as it renders all previously generated IDs impossible to decode.

    Testing

    In your tests you must hash the ID's before making the calls, because if you tell your Request class to decode an ID for you, it will throw an exception when the ID is not encoded.

    for Parameter ID's

    Always use getHashedKey() on your models when you want to get the ID

    Example:

    $data = [
    'roles_ids' => [
    $role1->getHashedKey(),
    $role2->getHashedKey(),
    ],
    'user_id' => $randomUser->getHashedKey(),
    ];
    $response = $this->makeCall($data);

    Or you can do this manually Hashids::encode($id);.

    for URL ID's

    You can use this helper function injectId($id, $skipEncoding = false, $replace = '{id}').

    Example:

    $response = $this->injectId($admin->id)->makeCall();

    More details on the Tests Helpers page.

    Availability

    You can use the Apiato\Core\Traits\HashIdTrait on any model or class, in order to have the encode and decode functions.

    By default, you have access to these functions $this->encode($id) and $this->decode($id) from all your Test classes and Controllers.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/pagination/index.html b/docs/11.x/core-features/pagination/index.html index 23d94ba85..572fef03e 100644 --- a/docs/11.x/core-features/pagination/index.html +++ b/docs/11.x/core-features/pagination/index.html @@ -4,7 +4,7 @@ Pagination | Apiato - + @@ -16,7 +16,7 @@ you can do it either project wide or per repository. After that a request can get all the data (with no pagination applied) by applying limit=0.

    This will return all matching entities:
    api.domain.test/endpoint?limit=0

    Project Wide

    Set PAGINATION_SKIP=true in .env file.

    Per Repository

    Override the $allowDisablePagination property in your specific Repository class.

    note

    Per repository configs override the global config and have precedence.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/profiler/index.html b/docs/11.x/core-features/profiler/index.html index 88e673daf..72eea91c5 100644 --- a/docs/11.x/core-features/profiler/index.html +++ b/docs/11.x/core-features/profiler/index.html @@ -4,14 +4,14 @@ Profiler | Apiato - +
    Version: 11.x

    Profiler

    Profiling is very important to optimize the performance of your application, and help you better understand what happens when a request is received, as well as it can speed up the debugging process.

    Apiato uses the third-party package laravel-debugbar (which uses the PHP Debug Bar), to collect the profiling data.

    By default, the laravel-debugbar package displays the profiling data in the browser. However, Apiato uses a middleware (ProfilerMiddleware) to append the profiling data to the response.

    Sample Profiler response

    {
    // Actual Response Here...
    "_profiler": {
    "__meta": {
    "id": "X167f293230e3457f1bbd95d9c82aba4a",
    "datetime": "2017-09-22 18:45:27",
    "utime": 1506105927.799299,
    "method": "GET",
    "uri": "/",
    "ip": "172.20.0.1"
    },
    "messages": {
    "count": 0,
    "messages": []
    },
    "time": {
    "start": 1506105922.742068,
    "end": 1506105927.799333,
    "duration": 5.057265043258667,
    "duration_str": "5.06s",
    "measures": [
    {
    "label": "Booting",
    "start": 1506105922.742068,
    "relative_start": 0,
    "end": 1506105923.524004,
    "relative_end": 1506105923.524004,
    "duration": 0.7819359302520752,
    "duration_str": "781.94ms",
    "params": [],
    "collector": null
    },
    {
    "label": "Application",
    "start": 1506105923.535343,
    "relative_start": 0.7932748794555664,
    "end": 1506105927.799336,
    "relative_end": 0.00000286102294921875,
    "duration": 4.26399302482605,
    "duration_str": "4.26s",
    "params": [],
    "collector": null
    }
    ]
    },
    "memory": {
    "peak_usage": 13234248,
    "peak_usage_str": "12.62MB"
    },
    "exceptions": {
    "count": 0,
    "exceptions": []
    },
    "route": {
    "uri": "GET /",
    "middleware": "api, throttle:30,1",
    "domain": "http://api.apiato.test",
    "as": "apis_root_page",
    "controller": "App\\Containers\\Welcome\\UI\\API\\Controllers\\Controller@apiRoot",
    "namespace": "App\\Containers\\Welcome\\UI\\API\\Controllers",
    "prefix": "/",
    "where": [],
    "file": "app/Containers/Welcome/UI/API/Controllers/Controller.php:20-25"
    },
    "queries": {
    "nb_statements": 0,
    "nb_failed_statements": 0,
    "accumulated_duration": 0,
    "accumulated_duration_str": "0μs",
    "statements": []
    },
    "swiftmailer_mails": {
    "count": 0,
    "mails": []
    },
    "logs": {
    "count": 3,
    "messages": [
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694807
    },
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694811
    },
    {
    "message": "[2017-09-18 17:38:15] testing.INFO: New User registration. ID = 970ylqvaogmxnbdr | Email = apiato@mail.test. Thank you for signing up.\n</div>\n</body>\n</html>\n \n",
    "message_html": null,
    "is_string": false,
    "label": "info",
    "time": 1506105927.694812
    }
    ]
    },
    "auth": {
    "guards": {
    "web": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]",
    "api": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]"
    },
    "names": ""
    },
    "gate": {
    "count": 0,
    "messages": []
    }
    }
    }

    Configuration

    By default, the profiler feature is turned off. To turn it on edit the .env file and set DEBUGBAR_ENABLED=true.

    To control and modify the profiler response, you need to edit this config file app/Ship/Configs/debugbar.php.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/query-parameters/index.html b/docs/11.x/core-features/query-parameters/index.html index 7f150f999..b520b04cc 100644 --- a/docs/11.x/core-features/query-parameters/index.html +++ b/docs/11.x/core-features/query-parameters/index.html @@ -4,7 +4,7 @@ Query Parameters | Apiato - + @@ -25,7 +25,7 @@ accepts driver as relationship ($availableIncludes in Transformer).

    Nested Includes

    It is also possible to request "nested includes". Extend the example from above. Imagine, that a Driver may also have a relationship to an Address object. You can access this information as well by calling ?include=driver,driver.address.

    Of course, the address include is defined in the respective DriverTransformer that is used here.

    Where to define the includes:

    Every Transformer can have 2 types of includes $availableIncludes and $defaultIncludes:

        protected $availableIncludes = [
    'products',
    'store',
    'recipients',
    ];

    protected $defaultIncludes = [
    'invoice',
    ];

    $defaultIncludes will not be listed in the response, only the $availableIncludes will be.

    Visit the Transformers page for more details.

    Skip caching

    (provided by the L5 Repository)

    Note: You need to turn the Eloquent Query Caching ON for this feature to work. ELOQUENT_QUERY_CACHE=true in .env.

    To run a new query and force disabling the cache on certain endpoints, you can use this parameter

    ?skipCache=true

    It's not recommended to keep skipping cache as it has bad impact on the performance.

    Configuration

    Most of these parameters are provided by the L5 Repository and configurable from the Ship/Configs/repository.php file. Some of them are built in house, or inherited from other packages such as Fractal.

    See the Query parameters from the User Developer perspective

    1) Generate the Default API documentation

    2) Visit the documentation URL

    More details in the API Docs Generator page.

    More Information

    For more details on these parameters check out these links:

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/rate-limiting/index.html b/docs/11.x/core-features/rate-limiting/index.html index 8917812e1..15225b2bf 100644 --- a/docs/11.x/core-features/rate-limiting/index.html +++ b/docs/11.x/core-features/rate-limiting/index.html @@ -4,14 +4,14 @@ Rate Limiting | Apiato - +
    Version: 11.x

    Rate Limiting

    Apiato uses the default Laravel middleware for rate limiting (throttling).

    All REST API requests are throttled to prevent abuse and ensure stability. The exact number of calls that your application can make per day varies based on the type of request you are making.

    The rate limit window is 1 minute per endpoint, with most individual calls allowing for 30 requests in each window.

    In other words, each user is allowed to make 30 calls per endpoint every 1 minute. (For each unique access token).

    To update these values go to app/Ship/Configs/apiato.php config file, or to the ENV file.

    'throttle' => [
    'enabled' => env('GLOBAL_API_RATE_LIMIT_ENABLED', true),
    'attempts' => env('GLOBAL_API_RATE_LIMIT_ATTEMPTS_PER_MIN', '30'),
    'expires' => env('GLOBAL_API_RATE_LIMIT_EXPIRES_IN_MIN', '1'),
    ]
    GLOBAL_API_RATE_LIMIT_ENABLED=true
    GLOBAL_API_RATE_LIMIT_ATTEMPTS_PER_MIN=30
    GLOBAL_API_RATE_LIMIT_EXPIRES_IN_MIN=1

    For how many hits you can preform on an endpoint, you can always check the header:

    X-RateLimit-Limit →30
    X-RateLimit-Remaining →29

    Enable/Disable Rate Limiting:

    The global API rate limiting middleware name is api and is enabled and applied to all the Container API Endpoints by default.

    Disable on specific endpoint:

    This middleware can be bypassed using withoutMiddleware() method on a specific route.

    Disable on all endpoints (globally):

    To disable it set GLOBAL_API_RATE_LIMIT_ENABLED to false in the .env file.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/useful-commands/index.html b/docs/11.x/core-features/useful-commands/index.html index 02943f27b..b01d2ad14 100644 --- a/docs/11.x/core-features/useful-commands/index.html +++ b/docs/11.x/core-features/useful-commands/index.html @@ -4,14 +4,14 @@ Useful Commands | Apiato - +
    Version: 11.x

    Useful Commands

    Apiato is loaded with many useful commands to help you speed up the development process. You can see list of all commands, by typing php artisan and look for Apiato section.

    Available Commands

    • php artisan apiato Display the current Apiato version.
    • php artisan apiato:apidoc Generate API Documentations with apidoc from your routes Docblock. More details.
    • php artisan apiato:create:admin Create a new User with the ADMIN role
    • php artisan apiato:generate:{component} Generate a specific component for the framework (e.g., Action, Task, ...). For more details on the Code Generator click here.
    • php artisan apiato:list:actions List all Actions in the Application.
    • php artisan apiato:list:tasks List all Tasks in the Application.
    • php artisan apiato:seed-deploy Seeds your custom deployment data from app/Ship/Seeders/SeedDeploymentData.php.
    • php artisan apiato:seed-test Seeds your custom testing data from app/Ship/Seeders/SeedTestingData.php.
    • php artisan apiato:welcome Just saying welcome from a container.
    - + \ No newline at end of file diff --git a/docs/11.x/core-features/user-registration/index.html b/docs/11.x/core-features/user-registration/index.html index 146a5352c..40299ffdf 100644 --- a/docs/11.x/core-features/user-registration/index.html +++ b/docs/11.x/core-features/user-registration/index.html @@ -4,13 +4,13 @@ User Registration | Apiato - +
    Version: 11.x

    User Registration

    Register users by credentials (email and passwords)

    Call the http://api.apiato.test/v1/register endpoint (you can find its documentation after generating the API Docs.

    Check out the registerUser endpoint in the API Routes files.

    This will register a new User and respond with user object.

    Registration request:

    curl --request POST \
    --url http://api.apiato.test/v1/register \
    --header 'accept: application/json' \
    --header 'content-type: application/x-www-form-urlencoded' \
    --data 'email=john%40doe.com&password=password&name=John%20Doe'

    Registration response:

    {
    "data": {
    "object": "User",
    "id": "XbPW7awNkzl83LD6",
    "name": "John Doe",
    "email": "john@doe.com",
    "email_verified_at": null,
    "gender": null,
    "birth": null,
    "created_at": "2021-04-15T14:17:24.000000Z",
    "updated_at": "2021-04-15T14:17:24.000000Z",
    "readable_created_at": "1 second ago",
    "readable_updated_at": "1 second ago"
    },
    "meta": {
    "include": [
    "roles"
    ],
    "custom": []
    }
    }

    Note: After registration in order to get the user access token you will have to send another call to http://api.example.com/v1/oauth/token with following fields and values

    username => your_username e.g. admin@admin.com
    password => your_password
    grant_type => password
    client_id => your_client_id
    client_secret => your_client_secret

    For third-party clients you must have client ID and secret first. You can generate them by creating new client in your app using Laravel Passport.

    For first-party clients you can use a proxy to add those fields on requests coming from your trusted client. For an example on how to do it look at ProxyLoginForWebClientAction Action in Authentication Container.

    Register users by Social Account

    (Facebook, Twitter, Google..)

    Checkout the Social Authentication Page for how to Sign up with Social Account.

    - + \ No newline at end of file diff --git a/docs/11.x/core-features/validation/index.html b/docs/11.x/core-features/validation/index.html index 88142d061..e92faf14a 100644 --- a/docs/11.x/core-features/validation/index.html +++ b/docs/11.x/core-features/validation/index.html @@ -4,13 +4,13 @@ Validation | Apiato - +
    Version: 11.x

    Validation

    Apiato uses the powerful Laravel validation system.

    In Apiato, validation must be defined in Request component, since every request might have different rules.

    Validation rules are automatically applied, once injecting the Request in the Controller.

    Requests help validating User data, accessibility, ownership and more.

    Example Request with Validation rules:

    namespace App\Containers\AppSection\User\UI\API\Requests;

    use App\Ship\Parents\Requests\Request;

    class RegisterUserRequest extends Request
    {
    /**
    * @return array
    */
    public function rules()
    {
    return [
    'email' => 'required|email|max:200|unique:users,email',
    'password' => 'required|min:20|max:300',
    'name' => 'required|min:2|max:400',
    ];
    }

    }

    Usage from Controller Example:

        public function registerUser(RegisterUserRequest $request)
    {
    $user = app(RegisterUserAction::class)->run($request);
    return $this->transform($user, UserTransformer::class);
    }

    Responses

    Validation Error response format:

    Single Field:

    {
    "message": "The given data was invalid.",
    "errors": {
    "email": [
    "The email has already been taken."
    ]
    }
    }

    Multiple Fields:

    {
    "message": "The given data was invalid.",
    "errors": {
    "email": [
    "The email has already been taken."
    ],
    "password": [
    "The password field is required."
    ]
    }
    }

    More details about requests in the Requests Page.

    - + \ No newline at end of file diff --git a/docs/11.x/faq/index.html b/docs/11.x/faq/index.html index f781e0930..f5c646387 100644 --- a/docs/11.x/faq/index.html +++ b/docs/11.x/faq/index.html @@ -4,7 +4,7 @@ Frequently Asked Questions | Apiato - + @@ -37,7 +37,7 @@ Discord.

    Lastly, if you got your question answered, consider sharing it, if you believe it can help others. You can submit a PR adding the questions and answer here on the FAQ page. Or leave it somewhere on the repository or on the chat room. Thanks in advance :)

    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/container-installer/index.html b/docs/11.x/getting-started/container-installer/index.html index 31d451277..6a2db4590 100644 --- a/docs/11.x/getting-started/container-installer/index.html +++ b/docs/11.x/getting-started/container-installer/index.html @@ -4,7 +4,7 @@ Container Installer | Apiato - + @@ -20,7 +20,7 @@ that allows installing/updating containers.
  • You must provide the key extra.apiato.container.name. This key indicates the name of the folder (e.g., container) when installing the package to the /app/Containers/Vendor folder. In the shown example, the container would be installed to app/Containers/Vendor/Foo.
  • - + \ No newline at end of file diff --git a/docs/11.x/getting-started/conventions-and-principles/index.html b/docs/11.x/getting-started/conventions-and-principles/index.html index 17f9f20ba..fafd589b1 100644 --- a/docs/11.x/getting-started/conventions-and-principles/index.html +++ b/docs/11.x/getting-started/conventions-and-principles/index.html @@ -4,13 +4,13 @@ Conventions | Apiato - +
    Version: 11.x

    Conventions

    HTTP Methods usage in RESTful API's

    • GET (SELECT): retrieve a specific resource from the server, or a listing of resources.
    • POST (CREATE): create a new resource on the server.
    • PUT (UPDATE): update a resource on the server, providing the entire resource.
    • PATCH (UPDATE): update a resource on the server, providing only changed attributes.
    • DELETE (DELETE): remove a resource from the server.

    Naming Conventions for Routes & Actions

    • GetAllResource: to fetch all resources.
    • FindResourceByID: to search for single resource by its unique identifier.
    • CreateResource: to create a new resource.
    • UpdateResource: to update/edit existing resource.
    • DeleteResource: to delete a resource.

    General guidelines and principles for RESTful URLs

    • A URL identifies a resource.
    • URLs should include nouns, not verbs.
    • Use plural nouns only for consistency (no singular nouns).
    • Use HTTP verbs (GET, POST, PUT, DELETE) to operate on the collections and elements.
    • You should not need to go deeper than resource/identifier/resource.
    • Put the version number at the base of your URL, for example http://apiato.test/v1/path/to/resource.
    • If an input data changes the logic of the endpoint, it should be passed in the URL. If not can go in the header "like Auth Token".
    • Don't use query parameters to alter state.
    • Don't use mixed-case paths if you can help it; lowercase is best.
    • Don't use implementation-specific extensions in your URIs (.php, .py, .pl, etc.)
    • Limit your URI space as much as possible. And keep path segments short.
    • Don't put metadata in the body of a response that should be in a header

    Good URL examples

    • Find a single Car by its unique identifier (ID):
      • GET http://www.api.apiato.test/v1/cars/123
    • Get all Cars:
      • GET http://www.api.apiato.test/v1/cars
    • Find/Search cars by one or more fields:
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes;color:white
    • Order and Sort query result:
      • GET http://www.api.apiato.test/v1/cars?orderBy=created_at&sortedBy=desc
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes&orderBy=created_at&sortedBy=desc
    • Specify optional fields:
      • GET http://www.api.apiato.test/v1/cars?filter=id;name;status
      • GET http://www.api.apiato.test/v1/cars/123?filter=id;name;status
    • Get all Drivers belonging to a Car:
      • GET http://www.api.apiato.test/v1/cars/123/drivers
      • GET http://www.api.apiato.test/v1/cars/123/drivers/123/addresses
    • Include Drivers objects relationship with the car response:
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers,owner
    • Add new Car:
      • POST http://www.api.apiato.test/v1/cars
    • Add new Driver to a Car:
      • POST http://www.api.apiato.test/v1/cars/123/drivers

    General principles for HTTP methods

    • Don't ever use GET to alter state; to prevent Googlebot from corrupting your data. And use GET as much as possible.
    • Don't use PUT unless you are updating an entire resource. And unless you can also legitimately do a GET on the same URI.
    • Don't use POST to retrieve information that is long-lived or that might be reasonable to cache.
    • Don't perform an operation that is not idempotent with PUT.
    • Use GET for things like calculations, unless your input is large, in which case use POST.
    • Use POST in preference to PUT when in doubt.
    • Use POST whenever you have to do something that feels RPC-like.
    • Use PUT for classes of resources that are larger or hierarchical.
    • Use DELETE in preference to POST to remove resources.
    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/installation/index.html b/docs/11.x/getting-started/installation/index.html index 5a5ee2a58..3b49badfc 100644 --- a/docs/11.x/getting-started/installation/index.html +++ b/docs/11.x/getting-started/installation/index.html @@ -4,7 +4,7 @@ Installation | Apiato - + @@ -28,7 +28,7 @@ try running this command homestead halt && homestead up --provision.

    Using anything else

    If you're not into virtualization solutions, you can set up your environment directly on your machine. Check the software's requirements list.

    Let's Play

    Now let's see it in action

    Open your web browser and visit:

    • http://apiato.test You should see an HTML page, with Apiato in the middle.
    • http://api.apiato.test You should see a response like this:
    [
    "Welcome to Apiato"
    ]

    Open your HTTP client and call:

    • http://api.apiato.test/ You should see a JSON response with message: "Welcome to apiato.",
    • http://api.apiato.test/v1 You should see a JSON response with message: "Welcome to apiato (API V1).",

    Make some HTTP calls to the API:

    To make the calls you can use Postman, HTTPIE or any other tool you prefer.

    Let's test the (user registration) endpoint http://api.apiato.test/v1/register with cURL:

    curl -X POST -H "Accept: application/json" -H "Cache-Control: no-cache" -F "email=John@Doe.me" -F "password=so-secret" -F "name=John Doe" "http://api.apiato.test/v1/register"

    You should get a response like this:

    Header:

    Access-Control-Allow-Origin → ...
    Cache-Control → ...
    Connection → keep-alive
    Content-Language → en
    Content-Type → application/json
    Date → Wed, 11 Apr 2000 22:55:88 GMT
    Server → nginx
    Transfer-Encoding → chunked
    Vary → Origin
    X-Powered-By → PHP/7.7.7
    X-RateLimit-Limit → 30
    X-RateLimit-Remaining → 29

    Body:

    {
    "data": {
    "object": "User",
    "id": "7VgmkMw7rR2pWO5j",
    "name": "John Doe",
    "email": "John@Doe.me",
    "email_verified_at": null,
    "gender": null,
    "birth": null,
    "created_at": "2021-04-12T13:33:24.000000Z",
    "updated_at": "2021-04-12T13:33:24.000000Z",
    "readable_created_at": "1 second ago",
    "readable_updated_at": "1 second ago"
    },
    "meta": {
    "include": [
    "roles"
    ],
    "custom": []
    }
    }
    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/markdown-features/index.html b/docs/11.x/getting-started/markdown-features/index.html index dc9ba010a..e167fd59c 100644 --- a/docs/11.x/getting-started/markdown-features/index.html +++ b/docs/11.x/getting-started/markdown-features/index.html @@ -4,13 +4,13 @@ Markdown Features | Apiato - +
    Version: 11.x

    Markdown Features

    Docusaurus supports the Markdown syntax and has some additional features.

    Front Matter

    Markdown documents can have associated metadata at the top called Front Matter:

    ---
    id: my-doc
    title: My document title
    description: My document description
    sidebar_label: My doc
    ---

    Markdown content

    Regular Markdown links are supported using url paths or relative file paths.

    Let's see how to [Create a page].
    Let's see how to [Create a page].

    Let's see how to [Create a page].

    Markdown images

    Regular Markdown images are supported.

    Add an image at static/img/logo.png and use this Markdown declaration:

    ![Docusaurus logo](/img/logo.png)

    Docusaurus logo

    Code Blocks

    Markdown code blocks are supported with Syntax highlighting.

    ```jsx title="src/components/HelloDocusaurus.js"
    function HelloDocusaurus() {
    return (
    <h1>Hello, Docusaurus!</h1>
    )
    }
    ```
    src/components/HelloDocusaurus.js
    function HelloDocusaurus() {
    return <h1>Hello, Docusaurus!</h1>;
    }

    Admonitions

    Docusaurus has a special syntax to create admonitions and callouts:

    :::tip My tip

    Use this awesome feature option

    :::

    :::danger Take care

    This action is dangerous

    :::
    My tip

    Use this awesome feature option

    Take care

    This action is dangerous

    React components

    Thanks to MDX, you can make your doc more interactive and use React components inside Markdown:

    export const Highlight = ({children, color}) => (
    <span
    style={{
    backgroundColor: color,
    borderRadius: '2px',
    color: 'red',
    padding: '0.2rem',
    }}>
    {children}
    </span>
    );

    <Highlight color="#25c2a0">Docusaurus green</Highlight> and <Highlight color="#1877F2">Facebook blue</Highlight> are my favorite colors.
    Docusaurus green and Facebook blue are my favorite colors.
    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/requests/index.html b/docs/11.x/getting-started/requests/index.html index 3b6bf8285..b5a6c8832 100644 --- a/docs/11.x/getting-started/requests/index.html +++ b/docs/11.x/getting-started/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -17,7 +17,7 @@ you can force your users to send application/json by setting 'force-accept-header' => true, in app/Ship/Configs/apiato.php or allow them to skip it completely by setting the 'force-accept-header' => false,. By default this flag is set to false.

    Calling Endpoints

    Calling unprotected endpoint example:

    curl -X POST -H "Accept: application/json" -H "Content-Type: application/x-www-form-urlencoded; -F "email=admin@admin.com" -F "password=admin" -F "=" "http://api.domain.test/v2/register"

    Calling protected endpoint (passing Bearer Token) example:

    curl -X GET -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "http://api.domain.test/v1/users"
    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/responses/index.html b/docs/11.x/getting-started/responses/index.html index 49f0ee0f6..1a5385ee8 100644 --- a/docs/11.x/getting-started/responses/index.html +++ b/docs/11.x/getting-started/responses/index.html @@ -4,7 +4,7 @@ Responses | Apiato - + @@ -17,7 +17,7 @@ If no $resourceKey is defined at the Model, the ShortClassName is used as key. For example, the ShortClassName of the App\Containers\AppSection\User\Models\User::class is User.

    Error Responses formats

    Visit each feature, e.g. the Authentication and there you will see how an unauthenticated response looks like, same for Authorization, Validation and so on.

    Building a Responses from the Controller:

    Checkout the Controller response builder helper functions.

    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/samples/index.html b/docs/11.x/getting-started/samples/index.html index eaa7f5413..d11182452 100644 --- a/docs/11.x/getting-started/samples/index.html +++ b/docs/11.x/getting-started/samples/index.html @@ -4,7 +4,7 @@ Samples | Apiato - + @@ -12,7 +12,7 @@
    Version: 11.x

    Samples

    The basic flow

    When an HTTP request is received, it first hits your predefined Endpoint (each endpoint live in its own Route file).

    Sample Route Endpoint

    Route::get('/hello', [Controller::class, 'sayHello']);

    After the user makes a request to the endpoint [GET] www.api.apiato.com/v1/hello it calls the defined controller function (sayHello).

    Sample Controller Function

    class Controller extends ApiController
    {
    public function sayHello(SayHelloRequest $request)
    {
    $helloMessage = app(SayHelloAction::class)->run();

    $this->json([
    $helloMessage
    ]);
    }
    }

    This function takes a Request class SayHelloRequest to automatically checks if the user has the right access to this endpoint. Only if the user has access, it proceeds to the function body.

    Then the function calls an Action (SayHelloAction) to perform the business logic.

    Sample Action

    class SayHelloAction extends Action
    {
    public function run()
    {
    return 'Hello World!';
    }
    }

    The Action can do anything then return a result (could be an Object, a String or anything).

    When the Action finishes its job, the controller function gets ready to build a response.

    Json responses can be built using the helper function json ($this->json(['foo' => 'bar']);).

    Sample User Response

    [
    "Hello World!"
    ]
    - + \ No newline at end of file diff --git a/docs/11.x/getting-started/software-architectural-patterns/index.html b/docs/11.x/getting-started/software-architectural-patterns/index.html index d2030108a..97cd39189 100644 --- a/docs/11.x/getting-started/software-architectural-patterns/index.html +++ b/docs/11.x/getting-started/software-architectural-patterns/index.html @@ -4,7 +4,7 @@ Architecture | Apiato - + @@ -14,7 +14,7 @@ App\Containers\SectionName\Printer).
  • Container MAY be named anything, however a good practice is to name it to its most important Model name. Example: If the User Story is (User can create a Store and Store can have Items) then we you could have 3 Containers (User, Store and Item).
  • - + \ No newline at end of file diff --git a/docs/11.x/index.html b/docs/11.x/index.html index 91b765da1..338ea0ccf 100644 --- a/docs/11.x/index.html +++ b/docs/11.x/index.html @@ -4,13 +4,13 @@ Requirements | Apiato - +
    Version: 11.x

    Requirements

    Requirements

    • GIT
    • PHP >= 8.0.2
    • Composer
    • PHP Extensions:
      • OpenSSL PHP Extension
      • PDO PHP Extension
      • Mbstring PHP Extension
      • Tokenizer PHP Extension
      • BCMath PHP Extension (required when the Hash ID feature is enabled)
      • Intl Extension (required when you use the Localization Container)
    - + \ No newline at end of file diff --git a/docs/11.x/main-components/actions/index.html b/docs/11.x/main-components/actions/index.html index 667cf0eff..a526c8432 100644 --- a/docs/11.x/main-components/actions/index.html +++ b/docs/11.x/main-components/actions/index.html @@ -4,7 +4,7 @@ Actions | Apiato - + @@ -17,7 +17,7 @@ automatically rolled-back from the database. However, respective operations on the file system (e.g., you may also have uploaded a profile picture for this Team already) need to be performed manually!

    Typically, you may want to use the transactionalRun() on the Controller level!

    - + \ No newline at end of file diff --git a/docs/11.x/main-components/controllers/index.html b/docs/11.x/main-components/controllers/index.html index c2e3d7e99..8c85a29ab 100644 --- a/docs/11.x/main-components/controllers/index.html +++ b/docs/11.x/main-components/controllers/index.html @@ -4,7 +4,7 @@ Controllers | Apiato - + @@ -17,7 +17,7 @@ This function allows including metadata in the response.

    $metaData = ['total_credits', 10000];

    return $this->withMeta($metaData)->transform($receipt, ReceiptTransformer::class);

    json This function allows passing array data to be represented as json.

    return $this->json([
    'foo': 'bar'
    ])

    Other functions

    • accepted
    • deleted
    • noContent
    • // Some functions might not be documented, so refer to the vendor/apiato/core/Traits/ResponseTrait.php and see the public functions.
    - + \ No newline at end of file diff --git a/docs/11.x/main-components/exceptions/index.html b/docs/11.x/main-components/exceptions/index.html index fb85f28c9..8ed32d46e 100644 --- a/docs/11.x/main-components/exceptions/index.html +++ b/docs/11.x/main-components/exceptions/index.html @@ -4,13 +4,13 @@ Exceptions | Apiato - +
    Version: 11.x

    Exceptions

    Definition & Principles

    Read Porto SAP Documentation (#Exceptions).

    Rules

    • All Exceptions MUST extend App\Ship\Parents\Exceptions\Exception.
    • Shared (general) Exceptions between all Containers SHOULD be created in the Exceptions Ship folder (app/Ship/Exceptions/*).
    • Every Exception SHOULD have two properties code and message. You can override those values while throwing the error.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Exceptions
    - AccountFailedException.php
    - ...

    - Ship
    - Exceptions
    - CreateResourceFailedException.php
    - NotFoundException.php
    - ...

    Code Samples

    Demo Exception

    class DemoException extends Exception
    {
    public $code = Response::HTTP_CONFLICT;
    public $message = 'This is a demo exception.';
    }

    Usage from anywhere

    throw new AccountFailedException();

    Usage with errors

    throw (new AccountFailedException())->withErrors(['email' => 'Email already in use']);
    throw (new AccountFailedException())->withErrors(['email' => ['Email already in use', 'Another message']]);

    Usage with errors and localization

    For localization, you can use the Localization Container

    // translation strings are automatically translated if the translations are found.
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken']);

    Response:

    {
    "message": "The exception error message.",
    "errors": {
    "email": [
    "The email has already been taken."
    ]
    }
    }

    Usage with Log for Debugging

    throw (new AccountFailedException())->debug($e); // debug() accepts string or \Exception instance

    Usage and overriding the default

    throw new AccountFailedException('I am the message to be displayed to the user');
    - + \ No newline at end of file diff --git a/docs/11.x/main-components/models/index.html b/docs/11.x/main-components/models/index.html index 450823c83..3864a6365 100644 --- a/docs/11.x/main-components/models/index.html +++ b/docs/11.x/main-components/models/index.html @@ -4,13 +4,13 @@ Models | Apiato - +
    Version: 11.x

    Models

    Definition & Principles

    Read Porto SAP Documentation (#Models).

    Rules

    • All Models MUST extend from App\Ship\Parents\Models\Model.
    • If the name of a model differs from the Container name you have to implement model() method in the repository - more details.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Models
    - User.php
    - UserId.php
    - ...

    Code Sample

    class Demo extends Model
    {
    protected $table = 'demos';

    protected $fillable = [
    'label',
    'user_id'
    ];

    protected $hidden = [
    'token',
    ];

    protected $casts = [
    'total_credits' => 'float',
    ];

    protected $dates = [
    'created_at',
    'updated_at',
    ];

    public function user()
    {
    return $this->belongsTo(\App\Containers\AppSection\User\Models\User::class);
    }
    }

    Notice the Demo Model has a relationship with User Model, which lives in another Container.

    Casts

    The casts attribute can be used to parse any of the model's attributes to a specific type. In the code sample below we can cast total_credits to float.

    More information about the applicable cast-types can be found in the laravel eloquent-mutators documentation.

    You can place any dates inside the $dates to parse those automatically.

    - + \ No newline at end of file diff --git a/docs/11.x/main-components/requests/index.html b/docs/11.x/main-components/requests/index.html index 7b722a70e..6f9eb9841 100644 --- a/docs/11.x/main-components/requests/index.html +++ b/docs/11.x/main-components/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -25,7 +25,7 @@ function.

    This helper, in turn, allows to "redefine" keys in the request for subsequent processing. Consider the following example request:

    {
    "data" : {
    "name" : "John Doe"
    }
    }

    Your Task to process this data, however, requests the field data.name as data.username. You can call the helper like this:

    $request->mapInput([
    'data.name' => 'data.username',
    ]);

    The resulting structure would look like this:

    {
    "data" : {
    "username" : "John Doe"
    }
    }

    Storing Data on the Request

    During the Request life-cycle you may want to store some data on the request object and pass it to other SubActions (or Tasks).

    To store some data on the request use:

    $request->keep(['someKey' => $someValue]);

    To retrieve the data back at any time during the request life-cycle use:

    $someValue = $request->retrieve('someKey');

    Unit Testing for Actions (Request)

    Since we're passing Request objects to Actions. When writing unit tests we need to create fake Request just to pass it to the Action with some fake data.

    // creating
    $request = RegisterUserRequest::injectData($data);

    Example One:

    $data = [
    'email' => 'john@doe.test',
    'name' => 'John Doe',
    'password' => 'so-secret',
    ];

    // create request object with some data
    $request = RegisterUserRequest::injectData($data);

    // create instance of the Action
    $action = app(RegisterUserAction::class)->run($request);

    // do any kind of assertions..
    $this->assertInstanceOf(User::class, $user);

    Example Two (With Authenticated User):

    $data = [
    'store_id' => $this->encode($store->id),
    'items' => $orderItems,
    'recipient' => $receipient,
    ];

    $user = User::factory()->create();

    $request = MakeOrderRequest::injectData($data, $user);

    $order = app(MakeOrderAction::class)->run($request);
    - + \ No newline at end of file diff --git a/docs/11.x/main-components/routes/index.html b/docs/11.x/main-components/routes/index.html index 158b8dfee..b5639dc08 100644 --- a/docs/11.x/main-components/routes/index.html +++ b/docs/11.x/main-components/routes/index.html @@ -4,13 +4,13 @@ Routes | Apiato - +
    Version: 11.x

    Routes

    Definition & Principles

    Read Porto SAP Documentation (#Routes).

    Rules

    • API Route files MUST be named according to their API's version, exposure and functionality. e.g. CreateOrder.v1.public.php, FulfillOrder.v2.public.php, CancelOrder.v1.private.php...

    • Web Route files are pretty similar to API web files, but they can be named anything.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - API
    - Routes
    - CreateItem.v1.public.php
    - DeleteItem.v1.public.php
    - CreateItem.v2.public.php
    - DeleteItem.v1.private.php
    - ApproveItem.v1.private.php
    - ...
    - WEB
    - Routes
    - main.php
    - ...

    Code Sample

    Web & API route

    Routes are defined exactly like the way you defined them in Laravel.

    Route::post('hello', [Controller::class, 'sayHello']);

    Protected Route (API)

    Route::get('users', [Controller::class, 'listAllUsers'])
    ->middleware(['auth:api']);

    Protect your Endpoints:

    Checkout the Authorization Page.

    Difference between Public & Private routes files

    Apiato has 2 types of endpoint, Public (External) mainly for third parties clients, and Private (Internal) for your own Apps. This will help to generate separate documentations for each and keep your internal API private.

    - + \ No newline at end of file diff --git a/docs/11.x/main-components/subactions/index.html b/docs/11.x/main-components/subactions/index.html index 452bbb058..179555eb1 100644 --- a/docs/11.x/main-components/subactions/index.html +++ b/docs/11.x/main-components/subactions/index.html @@ -4,13 +4,13 @@ Sub Actions | Apiato - +
    Version: 11.x

    Sub Actions

    Definition & Principles

    Read Porto SAP Documentation (#Sub-Actions).

    Rules

    • All SubActions MUST extend from App\Ship\Parents\Actions\SubAction.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Actions
    - ValidateAddressSubAction.php
    - BuildOrderSubAction.php
    - ...

    Code Sample

    ExampleSubAction

    class ExampleSubAction extends SubAction
    {
    public function run(SomeRequest $request)
    {
    app(SomeTask::class)->run($request);
    }
    }
    note

    Every feature available for Actions, are also available in SubActions.

    - + \ No newline at end of file diff --git a/docs/11.x/main-components/tasks/index.html b/docs/11.x/main-components/tasks/index.html index 579c00d2f..e909f0cce 100644 --- a/docs/11.x/main-components/tasks/index.html +++ b/docs/11.x/main-components/tasks/index.html @@ -4,13 +4,13 @@ Tasks | Apiato - +
    Version: 11.x

    Tasks

    Definition & Principles

    Read Porto SAP Documentation (#Tasks).

    Rules

    • All Tasks MUST extend from App\Ship\Parents\Tasks\Task.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Tasks
    - ConfirmUserEmailTask.php
    - GenerateEmailConfirmationUrlTask.php
    - SendConfirmationEmailTask.php
    - ValidateConfirmationCodeTask.php
    - SetUserEmailTask.php
    - ...

    Code Sample

    Task

    class FindUserByIdTask extends Task
    {
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    public function run($id)
    {
    try {
    $user = $this->userRepository->find($id);
    } catch (Exception $e) {
    throw new UserNotFoundException();
    }

    return $user;
    }
    }

    Task usage from an Action

    class ValidateUserEmailByConfirmationCodeAction extends Action
    {
    public function run($userId, $code)
    {
    app(ValidateConfirmationCodeTask::class)->run($userId, $code);
    $user = app(FindUserByIdTask::class)->run($userId);
    app(ConfirmUserEmailTask::class)->run($user);
    }
    }
    - + \ No newline at end of file diff --git a/docs/11.x/main-components/transformers/index.html b/docs/11.x/main-components/transformers/index.html index 3c6dac606..64ae4116e 100644 --- a/docs/11.x/main-components/transformers/index.html +++ b/docs/11.x/main-components/transformers/index.html @@ -4,13 +4,13 @@ Transformers | Apiato - +
    Version: 11.x

    Transformers

    Definition & Principles

    Read Porto SAP Documentation (#Transformers).

    Rules

    • All API responses MUST be formatted via a Transformer.

    • Every Transformer SHOULD extend from App\Ship\Parents\Transformers\Transformer.

    • Each Transformer MUST have a transform() function.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - API
    - Transformers
    - UserTransformer.php
    - ...

    Code Samples

    Reward Transformer with Country relation

    class ItemTransformer extends Transformer
    {
    protected $availableIncludes = [
    'images',
    ];

    protected $defaultIncludes = [
    'roles',
    ];

    public function transform(Item $item)
    {
    $response = [
    'object' => $item->getResourceKey(),
    'id' => $item->getHashedKey(),
    'name' => $item->name,
    'description' => $item->description,
    'price' => (float)$item->price,
    'weight' => (float)$item->weight,
    'created_at' => $item->created_at,
    'updated_at' => $item->updated_at,
    'readable_created_at' => $item->created_at->diffForHumans(),
    'readable_updated_at' => $item->updated_at->diffForHumans(),
    ];

    // add more or modify data for Admins only
    $response = $this->ifAdmin([
    'real_id' => $item->id,
    'deleted_at' => $item->deleted_at,
    ], $response);

    return $response;
    }

    public function includeImages(Item $item)
    {
    return $this->collection($item->images, new ItemImageTransformer());
    }

    public function includeRoles(User $user)
    {
    return $this->collection($user->roles, new RoleTransformer());
    }
    }

    Usage from Controller (Single Item)

    $user = $this->getUser();

    $this->transform($user, UserTransformer::class);

    // more options are available

    Relationships (include)

    Loading relationships in Transformer (calling other Transformers):

    This can be done in 2 ways:

    1. By the User, he can specify what relations to return in response.

    2. By the Developer, define what relations to include at run time.

    From Front-end

    You can request data with their relationships directly from the API call using include=tags,user but first the Transformer need to have the availableIncludes defined with their functions like this:

    class AccountTransformer extends Transformer
    {
    protected $availableIncludes = [
    'tags',
    'user',
    ];

    public function transform(Account $account)
    {
    return [
    'id' => (int)$account->id,
    'url' => $account->url,
    'username' => $account->username,
    'secret' => $account->secret,
    'note' => $account->note,
    ];
    }

    public function includeTags(Account $account)
    {
    // use collection with `multi` relationship
    return $this->collection($account->tags, new TagTransformer());
    }

    public function includeUser(Account $account)
    {
    // use `item` with single relationship
    return $this->item($account->user, new UserTransformer());
    }
    }

    Now to get the Tags with the response when Accounts are requested pass the ?include=tags parameter with the [GET] request.

    To get Tags with User use the comma separator: ?include=tags,user.

    From Back-end

    From the controller you can dynamically set the DefaultInclude using (setDefaultIncludes) anytime you want.

    return $this->transform($rewards, ProductsTransformer::class)->setDefaultIncludes(['tags']);

    You need to have includeTags function defined on the transformer. Look at the full examples above.

    If you want to include a relation with every response from this transformer you can define the relation directly in the transformer on ($defaultIncludes)

    protected $availableIncludes = [
    'users',
    ];

    protected $defaultIncludes = [
    'tags',
    ];

    // ..

    You need to have includeUser and includeTags functions defined on the transformer. Look at the full examples above.

    Transformer Available helper functions:

    • user(): returns current authenticated user object.

    • ifAdmin($adminResponse, $clientResponse): merges normal client response with the admin extra or modified results, when current authenticated user is Admin. Look at the full examples above.

    • nullableItem($model->something, new SomethingTransformer()): it is a shorthand for

    $model->something ? $this->item($model->something, new SomethingTransformer()) : $this->primitive(null)

    For more information about the Transformers read this.

    - + \ No newline at end of file diff --git a/docs/11.x/main-components/views/index.html b/docs/11.x/main-components/views/index.html index 0b507661a..d672edeff 100644 --- a/docs/11.x/main-components/views/index.html +++ b/docs/11.x/main-components/views/index.html @@ -4,13 +4,13 @@ Views | Apiato - +
    Version: 11.x

    Views

    Definition & Principles

    Read Porto SAP Documentation (#Views).

    Rules

    • Views SHOULD be created inside the Containers, and they will be automatically available for use in the Web Controllers.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - WEB
    - Views
    - welcome.php
    - profile.php
    - ...

    Code Sample

    Welcome page View

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome</title>
    </head>
    <body>
    <div class="container">
    <div class="content">
    <div class="title">Welcome</div>
    </div>
    </div>
    </body>
    </html>

    Usage From Controller

    class Controller extends WebController
    {
    public function sayWelcome()
    {
    return view('just-welcome');
    }
    }

    Namespaces

    • By default, all Views are namespaced as the camelCase of its Section name + @ + camelCase of its Container name.

    For example, a view named welcome-page inside MySection > MyContainer can be accessed like this: view(mySection@myContainer::welcome-page)

    If you try to access it without the namespace view('just-welcome'), it will not find your View.

    note

    View files in Ship folder are exception to this and will be namespaced with the word "ship" instead of section name, e.g. view(ship::welcome-page)

    - + \ No newline at end of file diff --git a/docs/11.x/miscellaneous/tasks-queuing/index.html b/docs/11.x/miscellaneous/tasks-queuing/index.html index 9936d35bd..b554139b6 100644 --- a/docs/11.x/miscellaneous/tasks-queuing/index.html +++ b/docs/11.x/miscellaneous/tasks-queuing/index.html @@ -4,7 +4,7 @@ Tasks Queuing | Apiato - + @@ -18,7 +18,7 @@

    The only addition to the Laravel's queues in Apiato, is that by default, apiato detects which queue driver you are planning to use (based on the configs), to create the migration files required, in case type database is used.

    if (Config::get('queue.default') == 'database')
    {
    // do something
    }

    (refer to app/Ship/Migrations/ folder for more details).

    Beanstalkd

    In order to use Beanstalkd as your queue driver, you need to require the "pda/pheanstalk": "^4.0" package first. You can include this in any composer.json file you want.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/miscellaneous/tasks-scheduling/index.html b/docs/11.x/miscellaneous/tasks-scheduling/index.html index 50149eb1f..59e864a11 100644 --- a/docs/11.x/miscellaneous/tasks-scheduling/index.html +++ b/docs/11.x/miscellaneous/tasks-scheduling/index.html @@ -4,7 +4,7 @@ Tasks Scheduling | Apiato - + @@ -18,7 +18,7 @@ See the Commands Page.

    Once you have your command ready, go to app/Ship/Kernels/ConsoleKernel.php and start adding the commands you need to schedule inside the schedule function.

    Example:

    protected function schedule(Schedule $schedule)
    {
    $schedule->command('apiato:welcome')->everyMinute();
    $schedule->job(new myJob)->hourly();
    $schedule->exec('touch me.txt')->dailyAt('12:00');
    // ...
    }

    More details here.

    note

    You do not need to register the commands with the $commands property or point to them in the commands() function. Apiato will do that automatically for you.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/miscellaneous/tests-helpers/index.html b/docs/11.x/miscellaneous/tests-helpers/index.html index 34588d2ce..1e83c48de 100644 --- a/docs/11.x/miscellaneous/tests-helpers/index.html +++ b/docs/11.x/miscellaneous/tests-helpers/index.html @@ -4,7 +4,7 @@ Tests Helpers | Apiato - + @@ -24,7 +24,7 @@ testing data.

    1. Go to app/Ship/Seeder/SeedTestingData.php seeder class, and create your live testing data.

    2. Run this command php artisan apiato:seed-test

    Debugging with PsySH

    For better debugging and development, you can open a runtime developer console while executing your test.

    Using PsySH (interactive debugger and REPL "read-eval-print loop" for PHP). The package is required by the Laravel Tinker Package.

    To use it set the breakpoint eval(\Psy\sh()); anywhere you want in any Actions, Controllers, Tasks... and run your test normally.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/commands/index.html b/docs/11.x/optional-components/commands/index.html index eafe8b1c3..113c08275 100644 --- a/docs/11.x/optional-components/commands/index.html +++ b/docs/11.x/optional-components/commands/index.html @@ -4,13 +4,13 @@ Commands | Apiato - +
    Version: 11.x

    Commands

    Definition

    • Commands are a Laravel artisan command. Laravel has its own default commands, and you can create your own as well.
    • Commands provide a way to interact with the Laravel app.
    • A Command can be scheduled by a Task scheduler, like Cron Job or by the Laravel built-in wrapper of the Cron Job "laravel scheduler".
    • Commands could be Closure based or Classes.
    • "dispatch" is the term that is usually used to call a Command.

    Principles

    • Containers MAY or MAY NOT have one or more Commands.

    • Every Command SHOULD call an Action to perform its job, and should not contain any business logic.

    • Ship may contain Application general Commands.

    Rules

    • All Commands MUST extend from App\Ship\Parents\Commands\ConsoleCommand.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - UI
    - CLI
    - Commands
    - SayHelloCommand.php
    - ...
    - Ship
    - Commands
    - GeneralCommand.php
    - ...

    Code Samples

    A Simple Command

    class HelloWorldCommand extends ConsoleCommand
    {
    protected $signature = 'hello:world';
    protected $description = 'Hello World!';

    public function handle()
    {
    echo "Hello World :)\n";
    }
    }

    Usage from CLI (Terminal)

    php artisan hello:world

    Schedule Commands Execution

    To Schedule the execution of a Command checkout the Tasks Scheduling page.

    Define Consoles Closure Commands

    To define Console closure commands go to app/Ship/Commands/closures.php.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/configs/index.html b/docs/11.x/optional-components/configs/index.html index fc02969bd..d7acad497 100644 --- a/docs/11.x/optional-components/configs/index.html +++ b/docs/11.x/optional-components/configs/index.html @@ -4,13 +4,13 @@ Configs | Apiato - +
    Version: 11.x

    Configs

    Definition

    Configs are files that contain configurations.

    In each Apiato container, there are two types of config files:

    • the container specific config file (a config file that contains the container specific configurations).
    • the container third party packages config files (a config file that belongs to a third party package, required by the composer file of the container).

    Principles

    • Your custom config files and third party packages config files, should be placed in the Container, unless it's too generic then it can be placed on the Ship Layer.
    • Containers can have as many config files as they need.

    Rules

    • When publishing a third party package config file, move it manually to its container or to the Ship Configs folder in case it is generic.
    • Framework config files (provided by Laravel) lives at the default config directory on the root of the project.
    • You SHOULD NOT add any config file to the root config directory.
    • The container specific config file, MUST be named this way:
      camelCase of its Section name + - + camelCase of its Container name, to prevent conflicts between third party packages and container specific packages.
      For example, config file inside MySection > MyContainer should be named like this: mySection-myContainer.php

    Folder Structure

    - app
    - Containers
    {section-name}
    - {container-name}
    - Configs
    - {section-name}-{container-name}.php
    - package-config-file1.php
    - ...
    - Ship
    - Configs
    - apiato.php
    - ...
    - config
    - app.php
    - ...

    Code Samples

    Example simple Config file

    // app/Containers/{SectionName}/{ContainerName}/Configs/{section-name}-{container-name}.php
    return [

    /*
    |--------------------------------------------------------------------------
    | Default Namespace
    |--------------------------------------------------------------------------
    */
    'namespace' => 'App',

    // some other config params here...

    You can access the respective configuration key like this:

    $value = Config::get('{section-name}-{container-name}.namespace');     // returns 'App'
    $value = config('{section-name}-{container-name}.namespace'); // same, but using laravel helper function
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/criterias/index.html b/docs/11.x/optional-components/criterias/index.html index 0b148fe25..3f75227f5 100644 --- a/docs/11.x/optional-components/criterias/index.html +++ b/docs/11.x/optional-components/criterias/index.html @@ -4,13 +4,13 @@ Criterias | Apiato - +
    Version: 11.x

    Criterias

    Definition

    Criterias are classes that hold and apply query condition when retrieving data from the database through a Repository.

    Without using a Criteria class, you can add your query conditions to a Repository or to a Model as scope, but with Criterias, your query conditions can be shared across multiple Models and Repositories. It allows you to define the query condition once and use it anywhere in the App.

    Principles

    • Every Container MAY have its own Criterias. However, shared Criterias SHOULD be created in the Ship layer.

    • A Criteria MUST not contain any extra code, if it needs data, the data SHOULD be passed to it from the Actions or the Task. It SHOULD not call any Task for data.

    Rules

    • All Criterias MUST extend from App\Ship\Parents\Criterias\Criteria.

    • Every Criteria SHOULD have an apply() function.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Criterias
    - ColourRedCriteria.php
    - RaceCarsCriteria.php
    - ...
    - Ship
    - Criterias
    - CreatedTodayCriteria.php
    - NotNullCriteria.php
    - ...

    Code Samples

    A Shared Criteria

    class OrderByCreationDateDescendingCriteria extends Criteria
    {
    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->orderBy('created_at', 'desc');
    }
    }

    Usage from Task

    public function run()
    {
    $this->userRepository->pushCriteria(new OrderByCreationDateDescendingCriteria());
    return $this->userRepository->paginate();
    }

    Criteria Accepting Data Input

    class ThisUserCriteria extends Criteria
    {
    private $userId;

    public function __construct($userId)
    {
    $this->userId = $userId;
    }

    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->where('user_id', '=', $this->userId);
    }
    }

    Passing Data from Task to Criteria

    public function run($user)
    {
    $this->accountRepository->pushCriteria(new ThisUserCriteria($user->id));
    return $this->accountRepository->paginate();
    }

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/events/index.html b/docs/11.x/optional-components/events/index.html index b681bbae3..8537b2792 100644 --- a/docs/11.x/optional-components/events/index.html +++ b/docs/11.x/optional-components/events/index.html @@ -4,13 +4,13 @@ Events | Apiato - +
    Version: 11.x

    Events

    Definition

    • Events provide a simple observer implementation, allowing you to subscribe and listen for various events that occur in your application.
    • Events are classes that can be fired from anywhere in your application.
    • An event class will usually be bound to one, or many Events Listeners Classes or has those Listeners registered to listen to it.
    • "fire" is the term that is usually used to call an Event.

    Principles

    • Events can be fired from Actions and or Tasks. It's preferable to choose one place only. (Tasks are recommended).
    • Events SHOULD be created inside the Containers. However, general Events CAN be created in the Ship layer.

    Rules

    • Event classes CAN be placed inside the Containers in Events folders or on the Ship for the general Events.
    • All Events MUST extend from App\Ship\Parents\Events\Event.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Events
    - SomethingHappenedEvent.php
    - ...
    - Listeners
    - ListenToMusicListener.php
    - ...

    - Ship
    - Events
    - GlobalStateChanged.php
    - SomethingBiiigHappenedEvent.php
    - ...

    Usage

    In Laravel, you can create and register events in multiple way. Read Laravel documentation to learn more about Events.

    Your custom EventServiceProvider needs to be registered in the containers MainServiceProvider as well.

    Broadcasting

    To define Broadcasting channels go to app/Ship/Boardcasts/channels.php.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/factories/index.html b/docs/11.x/optional-components/factories/index.html index e5465b0ae..d3e0a40ba 100644 --- a/docs/11.x/optional-components/factories/index.html +++ b/docs/11.x/optional-components/factories/index.html @@ -4,13 +4,13 @@ Factories | Apiato - +
    Version: 11.x

    Factories

    Definition

    Factories (are a short name for Model Factories).

    Factories are used to generate some fake data with the help of Faker to be used for testing purposes.

    Factories are mainly used from Tests.

    Principles

    • Factories SHOULD be created in the Containers.

    Rules

    • All Factories MUST extend from App\Ship\Parents\Factories\Factory.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Factories
    - UserFactory.php
    - ...

    Code Samples

    A User Model Factory

    class UserFactory extends Factory
    {
    protected $model = User::class;

    public function definition(): array
    {
    static $password;

    return [
    'name' => $this->faker->name,
    'email' => $this->faker->unique()->safeEmail,
    'password' => $password ?: $password = Hash::make('testing-password'),
    'email_verified_at' => now(),
    'remember_token' => Str::random(10),
    'is_admin' => false,
    ];
    }
    }

    Usage from Tests or Anywhere Else

    // creating 4 users
    User::factory()->count(4)->create();
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/helpers/index.html b/docs/11.x/optional-components/helpers/index.html index ce4a0a126..496b58e73 100644 --- a/docs/11.x/optional-components/helpers/index.html +++ b/docs/11.x/optional-components/helpers/index.html @@ -4,13 +4,13 @@ Helpers | Apiato - +
    Version: 11.x

    Helpers

    Definition

    • Helpers are global PHP functions that you can call from anywhere in your application.
    • Helper files are simple PHP files that hold functions.

    Principles

    • Helpers SHOULD be created inside the Containers. However, general Helpers CAN be created in the Ship layer.
    • You can create as many helper files as you need, per container.
    • You can implement as many helper functions as you need, per helper file.
    • All Helper files will be autoloaded by the framework.

    Rules

    • Helpers CAN be placed inside the Containers in Helpers folder or on the Ship for the general Helpers.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Helpers
    - helpers.php
    - mix.php
    - ...

    - Ship
    - Helpers
    - helpers.php
    - mix.php
    - ...

    Usage

    if (!function_exists('add')) {
    function add(int $firstNumber, int $secondNumber): int
    {
    return $firstNumber + $secondNumber;
    }
    }
    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/jobs/index.html b/docs/11.x/optional-components/jobs/index.html index 753c548e3..0d75c7bbb 100644 --- a/docs/11.x/optional-components/jobs/index.html +++ b/docs/11.x/optional-components/jobs/index.html @@ -4,13 +4,13 @@ Jobs | Apiato - +
    Version: 11.x

    Jobs

    Definition

    • Jobs are simple classes that can do one thing or multiple related things.
    • Job is a name given to a class that is usually created to be queued (it's execution is usually deferred for later, after the execution of previous Jobs are completed).
    • Jobs can be scheduled to be executed later by a queuing mechanism (a queue system like beanstalkd).
    • When a Job class is dispatched, it performs its specific job and dies.
    • Laravel's queue worker will process every Job as it's pushed onto the queue.

    Principles

    • A Container MAY have more than one Job.

    Rules

    • All Jobs MUST extend from App\Ship\Parents\Jobs\Job.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Jobs
    - DoSomethingJob.php
    - DoSomethingElseJob.php

    Code Samples

    DemoJob

    class DemoJob extends Job
    {
    private $something;

    public function __construct(array $someData)
    {
    $this->something = $someData;
    }

    public function handle()
    {
    foreach ($this->something as $thing) {
    // do whatever you like
    }
    }
    }

    Check the parent Job class.

    Usage from Action

    // using helper function
    dispatch(new DemoJob($someData));

    // manually
    app(\Illuminate\Contracts\Bus\Dispatcher\Dispatcher::class)->dispatch(New DemoJob($someData));

    Execute Jobs Execution

    For running your Jobs checkout the Tasks Queuing page.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/languages/index.html b/docs/11.x/optional-components/languages/index.html index 86b42ca4d..bb21e13ff 100644 --- a/docs/11.x/optional-components/languages/index.html +++ b/docs/11.x/optional-components/languages/index.html @@ -4,14 +4,14 @@ Languages | Apiato - +
    Version: 11.x

    Languages

    Definition

    Languages are not real Components, they are just files that holds translations.

    Rules

    • Languages CAN be placed inside the Containers. However, the default laravel resources/lang languages files are still loaded and can be used as well.

    • All Translations are namespaced as the camelCase of its Section name + @ + camelCase of its Container name.
      For example, translation key inside a translation file named messages inside MySection > MyContainer can be accessed like this: __(mySection@myContainer::messages.welcome)

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Resources
    - Languages
    - en
    - messages.php
    - users.php
    - ar
    - messages.php
    - users.php

    Usage

    Nothing much to show here, here's how you use translated strings:

    __('mySection@myContainer::messages.welcome');
    Further reading

    More info at Localization.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/mails/index.html b/docs/11.x/optional-components/mails/index.html index 9e65546d9..92d8c3556 100644 --- a/docs/11.x/optional-components/mails/index.html +++ b/docs/11.x/optional-components/mails/index.html @@ -4,13 +4,13 @@ Mails | Apiato - +
    Version: 11.x

    Mails

    Definition

    The Mail component allows you to describe an email and send it whenever needed.

    Principles

    • Containers MAY or MAY NOT have one or more Mail.

    • Ship may contain general Mails.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Mails\Mail.
    • Email Templates must be placed inside the Mail directory in a Templates directory app/Containers/{section}/{container}/Mails/Templates.

    Folder Structure

    - app
    - Containers
    - {section-name}
    - {container-name}
    - Mails
    - UserRegisteredMail.php
    - ...
    - Templates
    - user-registered.blade.php
    - ...
    - Ship
    - Mails
    - SomeMail.php
    - ...
    - Templates
    - some-template.blade.php
    - ...

    Code Samples

    A simple Mail

    class UserRegisteredMail extends Mail implements ShouldQueue
    {
    use Queueable;

    protected $user;

    public function __construct(User $user)
    {
    $this->user = $user;
    }

    public function build()
    {
    return $this->view('appSection@user::user-registered')
    ->to($this->user->email, $this->user->name)
    ->with([
    'name' => $this->user->name,
    ]);
    }
    }

    Usage from an Action

    Notifications can be sent from Actions or Tasks using the Mail Facade.

    Mail::send(new UserRegisteredMail($user));

    Email Templates

    Templates should be placed inside a folder Templates inside the Mail folder.

    To access a Mail template (same like accessing a web view) you must call the camelCase of its Section name + @ + camelCase of its Container name.

    In the example below we're using the user-registered.blade.php template in the AppSection Section > User Container.

    $this->view('appSection@user::user-registered');

    Configure Emails

    Open the .env file and set the from mail and address. This will be used globally whenever the from function is not called in the Mail.

    MAIL_FROM_ADDRESS=test@test.test
    MAIL_FROM_NAME="apiato"

    To use different email address in some classes add ->to($this->email, $this->name) to the build function in your Mail class.

    By default Apiato is configured to use Log Driver MAIL_DRIVER=log, you can change that from the .env file.

    Queueing A Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/middlewares/index.html b/docs/11.x/optional-components/middlewares/index.html index 56c62b5fa..8ebf163c2 100644 --- a/docs/11.x/optional-components/middlewares/index.html +++ b/docs/11.x/optional-components/middlewares/index.html @@ -4,7 +4,7 @@ Middlewares | Apiato - + @@ -12,7 +12,7 @@
    Version: 11.x

    Middlewares

    Definition

    Middleware provide a convenient mechanism for filtering HTTP requests entering your application.

    You can enable and disable Middlewares as you wish.

    Principles

    • There are two types of Middlewares, General (applied on all the Routes by default) and Endpoints Middlewares (applied on some Endpoints).

    • The Middlewares CAN be placed in Ship layer or Container layer depending on its roles.

    Rules

    • If a Middleware is written inside a Container then it MUST be registered inside that Container.

    • To register Middlewares in a Container the container needs to have a MiddlewareServiceProvider, and like all other Container Providers it MUST be registered in the MainServiceProvider of that Container.

    • General Middlewares SHOULD live in the Ship layer app/Ship/Middlewares/* and are registered in the app/Ship/Kernels/HttpKernel.

    • Third Party packages Middleware CAN be registered in Containers or on the Ship layer (wherever they make more sense). For example the jwt.auth middleware "provided by the JWT package" should be registered in the Authentication Container (Containers/AppSection/Authentication/Providers/MiddlewareServiceProvider.php).

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Middlewares
    - WebAuthentication.php
    - Ship
    - Middleware
    - Http
    - EncryptCookies.php
    - VerifyCsrfToken.php

    Code Sample

    Middleware Registration Inside the Container Example

    class MiddlewareServiceProvider extends MiddlewareProvider
    {
    protected array $middlewares = [
    // ..
    ];

    protected array $middlewareGroups = [
    'web' => [
    // ..
    ],
    'api' => [
    // ..
    ],
    ];

    protected array $routeMiddleware = [
    // apiato User Authentication middleware for Web Pages
    'guest' => RedirectIfAuthenticated::class
    ];
    }

    Middleware Registration Inside the Ship Layer (HTTP Kernel)

    class HttpKernel extends LaravelHttpKernel
    {
    /**
    * The application's global HTTP middleware stack.
    *
    * These middleware are run during every request to your application.
    *
    * @var array
    */
    protected $middleware = [
    // Laravel middleware's
    // \App\Http\Middleware\TrustHosts::class,
    TrustProxies::class,
    HandleCors::class,
    PreventRequestsDuringMaintenance::class,
    ValidatePostSize::class,
    TrimStrings::class,
    ConvertEmptyStringsToNull::class,
    ];

    /**
    * The application's route middleware groups.
    *
    * @var array
    */
    protected $middlewareGroups = [
    'web' => [
    EncryptCookies::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    // \Illuminate\Session\Middleware\AuthenticateSession::class,
    ShareErrorsFromSession::class,
    VerifyCsrfToken::class,
    SubstituteBindings::class,
    ],

    'api' => [
    // Note: The "throttle" Middleware is registered by the RoutesLoaderTrait in the Core
    SubstituteBindings::class,
    ValidateJsonContent::class,
    ProcessETagHeadersMiddleware::class,
    ProfilerMiddleware::class,
    ],
    ];

    /**
    * The application's route middleware.
    *
    * These middleware may be assigned to groups or used individually.
    *
    * @var array
    */
    protected $routeMiddleware = [
    'auth' => Authenticate::class,
    // 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => SetCacheHeaders::class,
    // Note: The "can" Middleware is registered by MiddlewareServiceProvider in Authorization Container
    // 'can' => \Illuminate\Auth\Middleware\Authorize::class,
    // Note: The "guest" Middleware is registered by MiddlewareServiceProvider in Authentication Container
    // 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => RequirePassword::class,
    'signed' => ValidateSignature::class,
    'throttle' => ThrottleRequests::class,
    'verified' => EnsureEmailIsVerified::class,
    ];

    /**
    * The priority-sorted list of middleware.
    *
    * Forces non-global middleware to always be in the given order.
    *
    * @var string[]
    */
    protected $middlewarePriority = [
    EncryptCookies::class,
    StartSession::class,
    ShareErrorsFromSession::class,
    Authenticate::class,
    ThrottleRequests::class,
    AuthenticateSession::class,
    SubstituteBindings::class,
    Authorize::class,
    ];
    }
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/migrations/index.html b/docs/11.x/optional-components/migrations/index.html index 4ca749dbe..38ecf6c1b 100644 --- a/docs/11.x/optional-components/migrations/index.html +++ b/docs/11.x/optional-components/migrations/index.html @@ -4,13 +4,13 @@ Migrations | Apiato - +
    Version: 11.x

    Migrations

    Definition

    Migrations (are the short name for Database Migrations).

    Migrations are the version control of your database. They are very useful for generating and documenting the database tables.

    Principles

    • Migrations SHOULD be created inside the Containers folders.

    • Migrations will be autoloaded by the framework.

    Rules

    • No need to publish the DB Migrations. Just run the artisan migrate command and Laravel will read the Migrations from the Containers.

    Folder Structure

       - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Migrations
    - 2200_01_01_000001_create_something_table.php
    - ...

    Code Samples

    User CreateDemoTable Migrations

    class CreateDemoTable extends Migration
    {
    public function up()
    {
    Schema::create('demos', function (Blueprint $table) {
    $table->increments('id');
    // ...
    $table->timestamps();
    $table->softDeletes();
    });
    }

    public function down()
    {
    Schema::drop('demos');
    }
    }

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/notifications/index.html b/docs/11.x/optional-components/notifications/index.html index 3fa3fc789..acc57ada4 100644 --- a/docs/11.x/optional-components/notifications/index.html +++ b/docs/11.x/optional-components/notifications/index.html @@ -4,7 +4,7 @@ Notifications | Apiato - + @@ -12,7 +12,7 @@
    Version: 11.x

    Notifications

    Definition

    Notifications allow you to inform the user about a state changes in your application.

    The Laravel notifications supports sending notifications across a variety of channels (mail, SMS, Slack, Database...).

    When using the Database channel, the notifications will be stored in a database to be displayed in your client interface.

    Principles

    • Containers MAY or MAY NOT have one or more Notification.

    • Ship MAY contain Application general Notifications.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Notifications\Notification.

    Folder Structure

    - app
    - Containers
    - {select-name}
    - {container-name}
    - Notifications
    - UserRegisteredNotification.php
    - ...
    - Ship
    - Notifications
    - SystemFailureNotification.php
    - ...

    Code Samples

    A Simple Notification

    class BirthdayReminderNotification extends Notification implements ShouldQueue
    {
    use Queueable;

    protected $notificationMessage;

    public function __construct($notificationMessage)
    {
    $this->notificationMessage = $notificationMessage;
    }

    public function toArray($notifiable)
    {
    return [
    'content' => $this->notificationMessage,
    ];
    }

    public function toMail($notifiable)
    {
    // $notifiable is the object you want to notify "e.g. user"
    return (new MailMessage)
    ->subject("Hello World")
    ->line("Hi, $notifiable->name")
    ->line($this->notificationMessage);
    }

    public function toSms($notifiable)
    {
    // ...
    }

    // ...
    }

    Usage from an Action or Task

    Notifications can be sent from Actions or Tasks using the Notification Facade.

    \Notification::send($user, new BirthdayReminderNotification($notificationMessage));

    Alternatively you can use the Illuminate\Notifications\Notifiable trait on the notifiable object "e.g. User" and then call it as follows:

    // call notify, found on the Notifiable trait
    $user->notify(new BirthdayReminderNotification($notificationMessage));

    Select Channels

    To select a notification channel, apiato have the app/Ship/Configs/notification.php config file where you can define the array of supported channels "e.g. SMS, Email, WebPush...", to be used for all your notifications.

    If you want to override the configuration for some notifications classes, or if you prefer to define the channels within each notification class itself, you can override the via function public function via($notifiable) in the notification class and define your channels.

    Checkout laravel notification channels for list of supported integrations.

    Queueing a Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    Use DB channel

    Generally you need to generate the notification migration php artisan notifications:table, then run php artisan migrate, however just running the migration command will do the job, since Apiato already adds the _create_notifications_table.php in the default migrations files directory app/Ship/Migrations/.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/providers/index.html b/docs/11.x/optional-components/providers/index.html index 26de0e08d..e32d8fa26 100644 --- a/docs/11.x/optional-components/providers/index.html +++ b/docs/11.x/optional-components/providers/index.html @@ -4,7 +4,7 @@ Providers | Apiato - + @@ -14,7 +14,7 @@ In apiato those providers have been renamed and moved to the Ship Layer app/Ship/Parents/Providers/*:

    • AppServiceProvider
    • RouteServiceProvider
    • AuthServiceProvider
    • BroadcastServiceProvider
    • EventsServiceProvider
    note

    You should not touch those providers, instead you have to extend them from a containers providers in order to modify them. Example: the app/Containers/AppSection/Authentication/Providers/AuthProvider.php is extending the AuthServiceProvider to modify it.

    Those providers are not auto registered by default, thus writing any code there will not be available, unless you extend them. Once extended the child Provider should be registered in its Container Main Provider, which makes its parent available.

    This rule does not apply to the RouteServiceProvider since it's required by Apiato, this Provider is registered by the ShipProvider.

    Check How Service Providers are auto-loaded.

    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/repositories/index.html b/docs/11.x/optional-components/repositories/index.html index fb0a2cee4..edfe5846f 100644 --- a/docs/11.x/optional-components/repositories/index.html +++ b/docs/11.x/optional-components/repositories/index.html @@ -4,13 +4,13 @@ Repositories | Apiato - +
    Version: 11.x

    Repositories

    Definition

    The Repository classes are an implementation of the Repository Design Pattern.

    Their major roles are separating the business logic from the data (or the data access Task).

    Repositories save and retrieves Models to/from the underlying storage mechanism.

    The Repository is used to separate the logic that retrieves the data and maps it to a Model, from the business logic that acts on the Model.

    Principles

    • Every Model SHOULD have a Repository.

    • A Model SHOULD always get accessed through its Repository. (Never accessed directly).

    Rules

    • All Repositories MUST extend from App\Ship\Parents\Repositories\Repository. Extending from this class will give you access to methods like (find, create, update and much more).

    • Repository name should be same as it's model name (model: Foo -> repository: FooRepository).

    • If a Repository belongs to a Model whose name is not equal to its Container name, then the Repository implement model() method like this.

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Repositories
    - UserRepository.php
    - ...

    Code Samples

    Demo Repository

    class DemoRepository extends Repository
    {
    protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];
    }

    Usage

    // paginate the data by 10
    $users = $userRepository->paginate(10);

    // search by 1 field
    $cars = $carRepository->findByField('colour', $colour);

    // searching multiple fields
    $offer = $offerRepository->findWhere([
    'offer_id' => $offer_id,
    'user_id' => $user_id,
    ])->first();

    //....

    Different Model and Container Name

    The model() method must be implemented when the model has different name than the container.

    class DemoRepository extends Repository
    {
    // ...

    public function model(): string
    {
    return Demo::class;
    }
    }

    Other Properties:

    API Query Parameters Property

    To enable query parameters (?search=text,...) in your API you need to set the property $fieldSearchable on the Repository class, to instruct the querying on your model. More details.

        protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];

    All other Properties

    Apiato uses the l5-repository package, to provide a lot of powerful features to the repository class.

    Further reading

    To learn more about all the properties you can use, visit the andersao/l5-repository package documentation.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/seeders/index.html b/docs/11.x/optional-components/seeders/index.html index 228873b9d..356902f5b 100644 --- a/docs/11.x/optional-components/seeders/index.html +++ b/docs/11.x/optional-components/seeders/index.html @@ -4,13 +4,13 @@ Seeders | Apiato - +
    Version: 11.x

    Seeders

    Definition

    Seeders (are a short name for Database Seeders).

    Seeders are classes made to seed the database with real data, this data usually should exist in the Application after the installation (Example: the default Users Roles and Permissions or the list of Countries).

    Principles

    • Seeders SHOULD be created in the Containers. (If the container is using a package that publishes a Seeder class, this class should be manually placed in the Container that make use of it. Do not rely on the package to place it in its right location).

    Rules

    • Seeders should be in the right directory inside the container to be loaded.

    • To avoid any conflict between containers seeders classes, you SHOULD always prepend the Seeders of each container with the container name. (Example: UserPermissionsSeeder, ItemPermissionsSeeder).

      note

      If 2 seeders classes have the same name but live in different containers, one of them will not be loaded. In these situations you can also prepend the seeder name with the section name

    • If you wish to order the seeding of the classes, you can just append _1, _2 to your classes.

    Folder Structure

     - App
    - Containers
    - {section-name}
    - {container-name}
    - Data
    - Seeders
    - ContainerNameRolesSeeder_1.php
    - ContainerNamePermissionsSeeder_2.php
    - ...

    Code Samples

    Demo Seeder

    class DemoSeeder_1 extends Seeder
    {
    public function run()
    {
    app(CreateRoleTask::class)->run('admin', 'Administrator', 'Administrator Role', 999);
    // ...
    }
    }
    note

    Same Seeder class is allowed to contain seeding for multiple Models.

    Run the Seeders

    After registering the Seeders you can run this command:

    php artisan db:seed

    Migrate & seed at the same time

    php artisan migrate --seed

    Testing Seeder Command

    It's useful sometimes to create a big set of testing data. apiato facilitates this task:

    1. Open app/Ship/Seeders/SeedTestingData.php and write your testing data here.
    2. Run this command any time you want this data available (example at staging servers):
    php artisan apiato:seed-test
    Further reading

    More info at Laravel Docs.

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/tests/index.html b/docs/11.x/optional-components/tests/index.html index f97063022..a73857c68 100644 --- a/docs/11.x/optional-components/tests/index.html +++ b/docs/11.x/optional-components/tests/index.html @@ -4,13 +4,13 @@ Tests | Apiato - +
    Version: 11.x

    Tests

    Definition

    Tests classes are created to test if the Application classes are working as expected.

    The two most essential Test types for this architecture are the Unit Tests and the Functional Tests. However, Integration and Acceptance Tests can be used as well.

    Principles

    • Containers MAY be covered by all types of Tests.

    • Use Functional Tests to test Container Routes are doing what's expected from them.

    • Use Unit Tests to test Container Actions and Tasks are doing what's expected from them.

    Rules

    • All Container Test classes SHOULD extend from a Container Internal TestCase class {container-name}/Tests/TestCase.php. The container TestCase MUST extend main TestCase on Ship layer App\Ship\Parents\Tests\PhpUnit\TestCase. (Adding functions to the container TestCase allows sharing those functions between all Test classes of the Container).

    Folder Structure

     - app
    - Containers
    - {section-name}
    - {container-name}
    - Tests
    - TestCase.php // the container test case
    - Unit
    - CreateUserTest.php
    - UpdateUserTest.php
    - ...
    - UI
    - API
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - WEB
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - CLI
    - Tests
    - Functional
    - BackupDataTest.php
    - ...

    Code Sample

    class DeleteUserTest extends TestCase
    {
    protected $endpoint = 'delete@v1/users/{id}';

    protected array $access = [
    'roles' => '',
    'permissions' => 'delete-users',
    ];

    public function testDeleteExistingUser()
    {
    $user = $this->getTestingUser();

    $response = $this->injectId($user->id)->makeCall();

    $response->assertStatus(204);
    }
    }

    See the Tests Helpers Page

    - + \ No newline at end of file diff --git a/docs/11.x/optional-components/values/index.html b/docs/11.x/optional-components/values/index.html index f80374a36..4e0efa104 100644 --- a/docs/11.x/optional-components/values/index.html +++ b/docs/11.x/optional-components/values/index.html @@ -4,7 +4,7 @@ Values | Apiato - + @@ -12,7 +12,7 @@
    Version: 11.x

    Values

    Definition & Principles

    Values are short names for the known "Value Objects" which are simple Objects, pretty similar to Models in the concept of representing data, but they do not get stored in the DB, thus they don't have ID's. They also do not hold functionality or change any state, they just hold data.

    A Value Object is an immutable object that is defined by its encapsulated attributes. We create Value Object when we need it to represent/serve/manipulate some data (attached as attributes), and we'll kill it later when we don't need it anymore, to recreate it again when needed.

    Rules

    • All Models MUST extend from App\Ship\Parents\Values\Value.

    Folder Structure

    - App
    - Containers
    - {section-name}
    - {container-name}
    - Values
    - Output.php
    - Region.php
    - ...

    Code Sample

    class Location extends Value
    {
    private $x = null;
    private $y = null;
    protected $resourceKey = 'locales';

    public function __construct($x, $y)
    {
    $this->x = $x;
    $this->y = $y;
    }

    public function getCoordinatesAsString()
    {
    return $this->x . ' - ' . $this->y;
    }
    }
    - + \ No newline at end of file diff --git a/docs/11.x/upgrade-guide/index.html b/docs/11.x/upgrade-guide/index.html index df3cb0114..874106065 100644 --- a/docs/11.x/upgrade-guide/index.html +++ b/docs/11.x/upgrade-guide/index.html @@ -4,7 +4,7 @@ Upgrade Guide | Apiato - + @@ -23,7 +23,7 @@ and the term New Project (referring to the new freshly installed Apiato 5.0).

    1) Download and install Apiato 5.0. See Application Setup.

    2) Delete the Containers directory app/Containers from the new project.

    3) Move the Containers directory app/Containers from the old project to the new project.

    4) Open this file app/Ship/composer.json in your old project and only copy the required dependencies, from the old project to the same file in the new project.

    5) Again, open the app/Ship/composer.json file in the new project, and remove the following dependencies: guzzlehttp/guzzle, prettus/l5-repository, barryvdh/laravel-cors, spatie/laravel-fractal, vinkla/hashids and johannesschobel/apiato-container-installer.

    6) Move and replace the following directories from the old project to the new project: config, public, resources, database and storage.

    7) Open config/app.php and replace App\Ship\Engine\Providers\PortoServiceProvider::class with Apiato\Core\Providers\ApiatoProvider::class.

    8) Move .gitignore, phpunit.xml and .env files, from the old project to the new project.

    9) Open the .env file on the new project and append this to it API_RATE_LIMIT_ENABLED=true.

    10) Open phpunit.xml file of the new project and delete this line from the file <file>./app/Ship/Engine/Loaders/FactoryMixer/FactoriesLoader.php</file>.

    11) If you had live testing data in your old project inside app/Ship/Seeders/Data/Testing/Seeders/TestingDataSeeder.php file, then copy that file content and past it in the new project inside app/Ship/Seeders/SeedTestingData.php. You will need to rename the class (not the file) from TestingDataSeeder to SeedTestingData, and you will need to update the namespace from namespace App\Ship\Seeders\Data\Testing\Seeders; to namespace App\Ship\Seeders;.

    12) If you ever used the HashIdTrait, you need to search and replace this namespace App\Ship\Engine\Traits\HashIdTrait with this Apiato\Core\Traits\HashIdTrait.

    13) Run composer update. If you got any error at this step, try to solve it or open an Issue.

    14) Move the .git directory from the old project to the new one. Add all changes git add . then commit git commit -m 'upgrade Apiato from 4.1 to 5.0'.

    15) Run your tests vendor/bin/phpunit.

    That's it :)

    How to manually upgrade older versions to 4.1?

    Use the Manual Upgrading Guide below.

    Manual Upgrading Guide

    These commands and examples, are compatible with the Apiato 8.0 upgrade. You can just copy/past.

    1) Checkout a new branch from your stable branch, to perform the upgrade.

    git checkout -b upgrade-apiato

    2) Configure a new remote (upstream) that points to the official Apiato repository.

    git remote add upstream https://github.com/apiato/apiato

    Verify the new upstream repository was added, by listing the current configured remote repositories.

    git remote -vv

    origin git@bitbucket.org:username/my-awesome-api.git (fetch)
    origin git@bitbucket.org:username/my-awesome-api.git (push)
    upstream git@github.com:apiato/apiato.git (fetch)
    upstream git@github.com:apiato/apiato.git (push)

    3) Checkout a new branch to hold the latest Apiato changes. This branch will be merged into your upgrade-apiato branch created above.

    git checkout -b apiato-{version}
    // Example: git checkout -b apiato-8.0

    4) Configure this branch to track an upstream specific branch.

    Replace {upstream-branch-name} with the branch name you want to upgrade to (for example 8.0).

    git fetch upstream {upstream-branch-name}
    // Example: git fetch upstream 8.0

    git branch --set-upstream-to upstream/{upstream-branch-name}
    // Example: git branch --set-upstream-to upstream/8.0

    Verify your local branch is tracking the Apiato specified upstream branch.

    git branch -vv

    apiato 77b4d945 [upstream/{upstream-branch-name}] ...
    master 77d302aa [origin/master] ...

    5) Make this branch identical to the remote upstream branch

    git reset --hard upstream/{upstream-branch-name}
    // Example: git reset --hard upstream/8.0

    Verify this branch now contains the latest changes from the upstream branch.

    git log

    6) Switch back to the upgrade-apiato branch

    git checkout upgrade-apiato

    7) Now lets merge the 2 branches. This step can be done in two ways:

    Option A: Merge all the changes together and solve the conflicts if any. (Recommended)

    You can execute the next command with different different parameters, below are 2 options to pick whatever feels safer to you. Do not execute both of them.

    A1: This will overwrite your changes with the upstream changes. (Try this first and if your tests failed then you can try the second one).

    git merge --allow-unrelated-histories --strategy-option=theirs apiato-{version}
    // Example: git merge --allow-unrelated-histories --strategy-option=theirs apiato-8.0

    A2: This will let you solve all the conflicts manually. (Can be the most secure choice, but it's time consuming as well.)

    git merge --allow-unrelated-histories apiato-{version}
    // Example: git merge --allow-unrelated-histories apiato-8.0

    Option B: Manually cherry pick the commits you likes to have:

    git log {upstream-branch-name}

    (copy each commit ID, one by one)

    git cherry-pick {commit-ID}

    (if you get any conflict solve it and keep moving)

    8) Compare your .env with the new .env-example and update it.

    9) Check everything is working. By running Composer install first then re-running your tests.

    • Read the changelog releases page.
    • You may want to update your custom containers dependencies, simply follow the composer install error outputs and bump each failing dependency. (Hint: visit each package releases page, and use the version which supports the supported version of Laravel).
    • You may need to fix the failing tests.
    composer install  &&  vendor/bin/phpunit

    10) Finally, merge the upgrade-apiato (which contains the upgraded changes) with your stable branch (could be master).

    git checkout master
    git merge upgrade-apiato

    php artisan -V

    Enjoy :)

    - + \ No newline at end of file diff --git a/docs/9.x/contribution-guide/index.html b/docs/9.x/contribution-guide/index.html index 5047beadf..75e10c492 100644 --- a/docs/9.x/contribution-guide/index.html +++ b/docs/9.x/contribution-guide/index.html @@ -4,7 +4,7 @@ Contribution Guide | Apiato - + @@ -43,7 +43,7 @@ 3 - Make sure you write a complete Changelog, in the release description. 4 - Change the default branch on github to that new branch. 5 - If you updated the documentation and you should! then visit the documentation repository and merge the PR into master.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/admin-dashboard/index.html b/docs/9.x/core-features/admin-dashboard/index.html index beba2942a..f92ad2351 100644 --- a/docs/9.x/core-features/admin-dashboard/index.html +++ b/docs/9.x/core-features/admin-dashboard/index.html @@ -4,13 +4,13 @@ Admin Dashboard | Apiato - +
    Version: 9.x

    Admin Dashboard

    Apiato does not recommend serving HTML pages. Instead, you should build your own Frontend App completely isolated from the Backend code.

    The provided Admin route

    How it works

    Visiting http://admin.apiato.test/dashboard will redirect you to a login page for admins.

    the default credentials are:

    It is up to you to build and customize your admin dashboard however you prefer.

    Change default Admin credentials

    you can change these default values from the seeder class in the Authorization container: app/Containers/Authorization/Data/Seeders/RolesAndPermissionsSeeder.php.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/api-docs-generator/index.html b/docs/9.x/core-features/api-docs-generator/index.html index bff231541..b250656ec 100644 --- a/docs/9.x/core-features/api-docs-generator/index.html +++ b/docs/9.x/core-features/api-docs-generator/index.html @@ -4,7 +4,7 @@ API Docs Generator | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    API Docs Generator

    Every great API needs a great Documentation.

    Apiato make writing and generating documentations very easy with the php artisan apiato:apidoc command.

    Alternatively you can generate a swagger doc from the apidoc comments, to do so run php artisan apiato:swagger command.

    Requirements

    • Install the ApiDocJs tool, the project directory

      • (npm install apidoc)
    • (Recommended) read the Routes page first.

    Usage

    1 - Write a PHP docblock on top of your endpoint like this:

    For more info about the parameters check out ApiDocJs documentation

    <?php

    /**
    * @apiGroup Authentication
    * @apiName UserLogin
    * @api {post} /users/login User Login
    * @apiDescription Description Here....
    * @apiVersion 1.0.0
    * @apiPermission none
    *
    * @apiHeader Accept application/json
    *
    * @apiParam {String} email
    * @apiParam {String} password
    *
    * @apiSuccessExample {json} Success-Response:
    * HTTP/1.1 200 OK
    * {
    * "data": {
    * "id": "owpzanmh",
    * "name": "Super Admin",
    * "email": "admin@admin.com"
    * ...
    * }
    *
    * @apiErrorExample {json} Error-Response:
    * {
    * "message":"401 Credentials Incorrect.",
    * "status_code":401
    * }
    *
    * @apiErrorExample {json} Error-Response:
    * {
    * "message":"Invalid Input.",
    * "errors":{
    * "email":[
    * "The email field is required."
    * ]
    * },
    * "status_code":422
    * }
    */

    $router->post('users/login', [
    'uses' => 'Controller@userLogin',
    ]);

    Note: All the Endpoints DocBlocks MUST be written inside Routes files, otherwise they won't be loaded.

    2 - Run the documentation generator command from the root directory:


    php artisan apiato:apidoc

    3 - Visit this URL's as shown in your terminal:

    NOTE: Every time you change the DocBlock of a Route file you need to run the apiato:apidoc command, to regenerate.

    Generate Swagger/OpenAPI JSON schema from apiDoc

    It's also possible to generate a Swagger/OpenAPI JSON schema from apiDoc with:


    php artisan apiato:swagger

    You can find the JSON schema at http://apiato.test/api/private/documentation/swagger/swagger.json

    Error: ApiDoc not found !!

    If you get an error (apidoc not found),

    1. open the container config file Containers/Documentation/Configs/apidoc.php

    2. edit the executable path to $(npm bin)/apidoc or to however you access the apidoc tool on your machine.

    <?php
    /*
    |--------------------------------------------------------------------------
    | Executable
    |--------------------------------------------------------------------------
    |
    | Specify how you run or access the `apidoc` tool on your machine.
    |
    */

    'executable' => 'apidoc',

    Shared response for faster updating and less outdated responses:

    To prevent duplicating the responses between routes, let's create a shared response for each object.

    Example: _user.v1.public.php will contain all responses (single, multiple...) of the User:

    <?php

    /**
    * @apiDefine UserSuccessSingleResponse
    * @apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {
    "data":{
    "object":"User",
    "id":eqwja3vw94kzmxr0,
    },
    "meta":{
    "include":[],
    "custom":[]
    }
    }
    */

    Usage of the shared User response from any endpoint:

    * @apiUse UserSuccessSingleResponse

    To avoid having to generate and update the Single and Multiple responses of the same object (recommended only for private API's) you can use the general shared Multiple Response * @apiUse GeneralSuccessMultipleResponse which you can find and modify it from app/Containers/Documentation/UI/API/Routes/*

    Edit the default generated values in the templates:

    Apiato generates by defaults 2 API documentations, each one has its own apidoc.json file. Both can be modified from the Documentation Containers in Containers/Documentation/ApiDocJs/

    apidoc.json Example file:

    {
    "name": "Apiato",
    "description": "Apiato (Private API) Documentation",
    "title": "Welcome to Apiato",
    "version": "1.0.0",
    "url" : "http://api.apiato.test",
    "template": {
    "withCompare": true,
    "withGenerator": true
    },
    "header": {
    "title": "API Overview",
    "filename": "app/Containers/Documentation/ApiDocJs/private/header.md"
    },
    "footer": {
    "title": "Footer",
    "filename": "app/Containers/Documentation/ApiDocJs/private/header.md"
    },
    "order": [

    ]
    }

    Change the Documentations URL's

    Edit the config file of the Documentation Container Containers/Documentation/Configs/apidoc.php

    <?php

    return [

    /*
    |--------------------------------------------------------------------------
    | Executable
    |--------------------------------------------------------------------------
    |
    | Specify how you run or access the `apidoc` tool on your machine.
    |
    */

    'executable' => 'apidoc',

    /*
    |--------------------------------------------------------------------------
    | API Types
    |--------------------------------------------------------------------------
    |
    | The `types` helps generating multiple documentations, by grouping them
    | under types names. You can add or remove any type. By default
    | `public` and `private` types are set.
    |
    | url: The url to access that generated API documentation.
    |
    | routes: The route file to read when generating this documentation.
    | Every route file will have the following name format:
    | `{endpoint-name}.v{version-number}.{documentation-type}.php`.
    |
    */

    'types' => [

    'public' => [
    'url' => 'api/documentation',
    'routes' => [
    'public',
    ],
    ],

    'private' => [
    'url' => 'api/private/documentation',
    'routes' => [
    'private',
    'public',
    ],
    ],
    ],


    /*
    |--------------------------------------------------------------------------
    | HTML files
    |--------------------------------------------------------------------------
    |
    | Specify where to put the generated HTML files.
    |
    */

    'html_files' => 'public/'


    // ...
    ];

    Edit the Documentation Header

    The header is usually the Overview of your API. It contains Info about authenticating users, making requests, responses, potential errors, rate limiting, pagination, query parameters and anything you want.

    All this information is written in app/Containers/Documentation/ApiDocJs/shared/header.template.md file, and the same file is used as header for both private and public documentations.

    To edit the content just open the markdown file in any markdown editor and edit it.

    You will notice some variables like {{rate-limit}} and {{token-expires}}. Those are replaced when running apidoc:generate with real values from your application configuration files.

    Feel free to extend them to include more info about your API from the app/Containers/Documentation/Actions/ProcessMarkdownTemplatesAction.php class.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/api-versioning/index.html b/docs/9.x/core-features/api-versioning/index.html index f7f4818ec..d8b5e0e78 100644 --- a/docs/9.x/core-features/api-versioning/index.html +++ b/docs/9.x/core-features/api-versioning/index.html @@ -4,13 +4,13 @@ API Versioning | Apiato - +
    Version: 9.x

    API Versioning

    Since Laravel does not support API versioning, apiato provide a very easy way to implement versioning for your API.

    How it works

    Create:

    When creating a new API endpoint, specify the version number in the route file name following this naming format {endpoint-name}.{version-number}.{documentation-name}.php.

    Example:

    • MakeOrder.v1.public.php
    • MakeOrder.v2.public.php
    • ListOrders.v1.private.php

    Use:

    Automatically the endpoint inside that route file will be accessible by adding the version number to the URL.

    Example:

    • http://api.apiato.test/v1/register
    • http://api.apiato.test/v1/orders
    • http://api.apiato.test/v2/stores/123

    Version the API in header instead of URL

    First remove the URL version prefix:

    1. Edit app/Ship/Configs/apiato.php, set prefix to 'enable_version_prefix' => 'false',.
    2. Implement the Header versioning anyway you prefer. (this is not implemented in Apiato yet. Consider a contribution).
    - + \ No newline at end of file diff --git a/docs/9.x/core-features/authentication/index.html b/docs/9.x/core-features/authentication/index.html index 860d41e2f..8a5369f42 100644 --- a/docs/9.x/core-features/authentication/index.html +++ b/docs/9.x/core-features/authentication/index.html @@ -4,7 +4,7 @@ Authentication | Apiato - + @@ -60,7 +60,7 @@ It will send you an email with a link when you make a request to that link, it will call the /password-reset endpoint.

    Note: For security reason, make sure the reset password URL is set in app/Containers/User/Configs/user-container.php, and given to the client App, to be sent as parameter when calling the /password-forgot.

    Note: You must set up the email to get this function to work, however for testing purposes set the MAIL_DRIVER=log in your .env file in order to the see the email content in the log file laravel.log.

    Social Authentication

    For Social Authentication visit the Social Authentication page.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/authorization/index.html b/docs/9.x/core-features/authorization/index.html index b7a0f6f18..874683a4d 100644 --- a/docs/9.x/core-features/authorization/index.html +++ b/docs/9.x/core-features/authorization/index.html @@ -4,7 +4,7 @@ Authorization | Apiato - + @@ -15,7 +15,7 @@ User has a permission to read articles, moderator can manage comments and admin can create articles. User has a level 1, moderator level 2 and admin level 3. It means, moderator and administrator has also permission to read articles, but administrator can manage comments as well.

    if ($user->getRoleLevel() > 10) {
    //
    }

    If user has multiple roles, the getRoleLevel() method returns the highest one.

    If you don't need the permissions inheriting feature, simply ignore the optional level parameter when creating roles.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/code-generator/index.html b/docs/9.x/core-features/code-generator/index.html index 22ba95796..40f6a8b9d 100644 --- a/docs/9.x/core-features/code-generator/index.html +++ b/docs/9.x/core-features/code-generator/index.html @@ -4,7 +4,7 @@ Code Generator | Apiato - + @@ -17,7 +17,7 @@ file needs to be the same as in vendor/apiato/core/Generator/Stubs.

    Say, if you like to change the config.stub, simply copy the file to app/Ship/Generators/CustomStubs/config.stub and start adapting it to your needs.

    If you run the respective command (e.g., in this case php artisan apiato:generate:configuration) this would read your specific config.stub file instead the pre-defined one!

    Contributing

    If you would like to add your own generators, please check out the Contribution Guide.

    For AngularJS 2 Users

    Checkout this awesome CRUD Containers generator package for Angular 2.4+.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/data-caching/index.html b/docs/9.x/core-features/data-caching/index.html index 5b0905b48..30a7b4e6e 100644 --- a/docs/9.x/core-features/data-caching/index.html +++ b/docs/9.x/core-features/data-caching/index.html @@ -4,13 +4,13 @@ Data Caching | Apiato - +
    Version: 9.x

    Data Caching

    Enable / Disable Eloquent Query Caching

    By default caching is disabled.

    To enable it, go to app/Ship/Configs/repository.php config file and set cache > enabled => true, or set it from the .env file using ELOQUENT_QUERY_CACHE.

    More details can be found here.

    Users can skip the query caching and request new data by passing specific parameter to the Endpoint. Checkout the Query parameters page.

    Change different caching settings

    You can use different cache setting for each repository.

    To set cache settings on each repository, first the caching must be enabled, second you need to set some properties on the repository class to override the default values.

    For an example look at the app/Containers/Countries/Data/Repositories/CountryRepository.php class. For more details about all the properties refer to the L5 repository package documentation.

    Note: you don't need to use the CacheableRepository trait or implement the CacheableInterface since they both exist on the Abstract repository class (App\Ship\Parents\Repositories\Repository).

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/default-endpoints/index.html b/docs/9.x/core-features/default-endpoints/index.html index 56b2f2bf5..ba6bc9169 100644 --- a/docs/9.x/core-features/default-endpoints/index.html +++ b/docs/9.x/core-features/default-endpoints/index.html @@ -4,13 +4,13 @@ Default Endpoints | Apiato - +
    Version: 9.x

    Default Endpoints

    Apiato comes shipped with many useful API endpoints for speeding up the development process.

    You can see the endpoints in three ways:

    • In Terminal, by running php artisan route:list.
    • In Browser, by generating the beautiful detailed documentation. See the API Docs Generator page for more details.
    • In Code, by navigating to the Routes folder of each container's UI.

    Screenshot showing the result of route:list:

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/etag/index.html b/docs/9.x/core-features/etag/index.html index fa9ed8a90..5d79b2ba4 100644 --- a/docs/9.x/core-features/etag/index.html +++ b/docs/9.x/core-features/etag/index.html @@ -4,7 +4,7 @@ ETag | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    ETag

    ETag Middleware

    Apiato provides an ETag Middleware (app/Ship/Middlewares/Http/ProcessETagHeadersMiddleware.php) that implements the Shallow technique. It can be used to reduce bandwidth on the client side (especially for Mobile devices).

    By default, the feature is disabled. To enable it go to app/Ship/Configs/apiato.php and set use-etag to true. Of course your client should send the If-None-Match HTTP Header (= etag) in his request for this feature to work properly.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/hash-id/index.html b/docs/9.x/core-features/hash-id/index.html index 485d58792..e37343d06 100644 --- a/docs/9.x/core-features/hash-id/index.html +++ b/docs/9.x/core-features/hash-id/index.html @@ -4,13 +4,13 @@ Hash ID | Apiato - +
    Version: 9.x

    Hash ID

    Hashing your internal ID's, is a very helpful feature for security reasons (to prevent some hack attacks) and business reasons (to hide the real total records from your competitors).

    Enable Hash ID

    Set the HASH_ID=true in the .env file.

    Also, with the feature make sure to always use the getHashedKey() on any model, whenever you need to return an ID (mainly from transformers) weather hashed ID or not.

    Example:


    'id' => $user->getHashedKey(),

    Note: if the feature is set to false HASH_ID=false the getHashedKey() will return the normal ID.

    Usage

    There are 2 ways an ID's can be passed to your system via the API:

    In URL example: www.apiato.test/items/abcdef.

    In parameters example: [GET] or [POST] www.apiato.test/items?id=abcdef.

    in both cases you will need to inform your API about what's coming form the Request class.

    Checkout the Requests page. After setting the $decode and $urlParameters properties on your Request class, the ID will be automatically decoded for you, to apply validation rules on it or/and use it from your controller ($request->id will return the decoded ID).

    Configuration

    You can change the default length and characters used in the ID from the config file app/Ship/Configs/hashids.phpor in the .env file by editing the HASH_ID_LENGTH value.

    From Apiato Version 7.4.*

    You can set the HASH_ID_KEY in the .env file to any random string. You can generate this from any of the online random string generators, or run head /dev/urandom | tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_{|}~' | head -c 32 ; echo on the linux commandline. Apiato defaults to the APP_KEY should this not be set.

    The HASH_ID_KEY acts as the salt during hashing of the ID. This should never be changed in production as it renders all previously generated IDs quite difficult to decode and recover.

    Testing

    In your tests you must hash the ID's before making the calls, because if you tell your Request class to decode an ID for you, it will throw an exception when the ID is not encoded.

    for Parameter ID's

    Always use getHashedKey() on your models when you want to get the ID

    Example:

    $data = [
    'roles_ids' => [
    $role1->getHashedKey(),
    $role2->getHashedKey(),
    ],
    'user_id' => $randomUser->getHashedKey(),
    ];
    $response = $this->makeCall($data);

    Or you can do this manually Hashids::encode($id);.

    for URL ID's

    You can use this helper function injectId($id, $skipEncoding = false, $replace = '{id}').

    Example:

    $response = $this->injectId($admin->id)->makeCall();

    More details on the Tests Helpers page.

    Availability

    You can use the Apiato\Core\Traits\HashIdTrait to any model or class, in order to have the encode and decode functions.

    By default, you have access to these functions $this->encode($id) and $this->decode($id) from all your Tests class and Controllers.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/localization/index.html b/docs/9.x/core-features/localization/index.html index be0ba2e22..9b4b2e27d 100644 --- a/docs/9.x/core-features/localization/index.html +++ b/docs/9.x/core-features/localization/index.html @@ -4,7 +4,7 @@ Localization | Apiato - + @@ -36,7 +36,7 @@ language in this specific language (e.g., locale_name => Deutsch). Furthermore, the language name is outputted in the applications default name (e.g., configured in app.locale). This would result in default_name => German.

    The same applies to the regions that are defined (e.g., de-DE). Consequently, this results in locale_name => Deutschland and default_name = Germany.

    Tests

    To change the default language in your tests requests. You can set the env language in the phpunit.xml file.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/pagination/index.html b/docs/9.x/core-features/pagination/index.html index c1cbfceb2..eb7306a52 100644 --- a/docs/9.x/core-features/pagination/index.html +++ b/docs/9.x/core-features/pagination/index.html @@ -4,7 +4,7 @@ Pagination | Apiato - + @@ -16,7 +16,7 @@ you can manually override the $allowDisablePagination property in your specific Repository class. A requester can then get all data (with no pagination applied) by requesting api.domain.test/endpoint?limit=0. This will return all matching entities.

    Skip the Pagination Limit

    You can allow developers to skip the pagination limit as follows:

    First, you need to enable that feature from the server by setting PAGINATION_SKIP to true (PAGINATION_SKIP=true).

    Second, inform the developers (users) to pass ?limit=0 with the request they wish to get all it's data un-paginated.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/payments/index.html b/docs/9.x/core-features/payments/index.html index 6525d4867..4f5c27baa 100644 --- a/docs/9.x/core-features/payments/index.html +++ b/docs/9.x/core-features/payments/index.html @@ -4,7 +4,7 @@ Payments | Apiato - + @@ -38,7 +38,7 @@ need to create your own ChargeWithFooTask. This class, however, needs to implement the PaymentChargerInterface distributed via the Payment container. This interface, in turn, requires you to implement the charge() method.

    This method needs to connect to the FooService, create the payment and return a PaymentTransaction model.

    Finally, you need to register the new service. This can be done in the Payment\Configs\payment-container.php file. For the payment-container.gateways key, add the new entry for your Foo Payment Gateway. This may look like this:

        // ...
    'foo' => [
    'container' => 'Foo',
    'charge_task' => \App\Containers\Foo\Tasks\ChargeWithFooTask::class,
    ],
    // ...

    Basically, this entry points to the charger_task that handles, how to charge a User with the specific Payment Gateway.

    That's all!

    Mocking the real payment call for Testing

    <?php

    // mock the ChargeWithStripeService external API call
    $this->mockIt(ChargeWithStripeService::class)->shouldReceive('charge')->andReturn([
    'payment_method' => 'stripe',
    'description' => $payId
    ]);

    // mock the ChargeWithPaypalService external API call
    $this->mockIt(ChargeWithPaypalService::class)->shouldReceive('charge')->andReturn([
    'payment_method' => 'paypal',
    'description' => $payId
    ]);

    Checkout the Tests Helpers page for about Testing.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/profiler/index.html b/docs/9.x/core-features/profiler/index.html index 8bb4c92ea..7852dccc3 100644 --- a/docs/9.x/core-features/profiler/index.html +++ b/docs/9.x/core-features/profiler/index.html @@ -4,14 +4,14 @@ Profiler | Apiato - +
    Version: 9.x

    Profiler

    Profiling is very important to optimize the performance of your application, and help you better understand what happens when a request is received, as well as it can speed up the debugging process.

    Apiato uses the third-party package laravel-debugbar (which uses the PHP Debug Bar), to collect the profiling data.

    By default, the laravel-debugbar package displays the profiling data in the browser. However, Apiato uses a middleware app/Ship/Middlewares/Http/ProfilerMiddleware.php to append the profiling data to the response.

    Sample Profiler response

    {
    // Actual Response Here...
    "_profiler": {
    "__meta": {
    "id": "X167f293230e3457f1bbd95d9c82aba4a",
    "datetime": "2017-09-22 18:45:27",
    "utime": 1506105927.799299,
    "method": "GET",
    "uri": "/",
    "ip": "172.20.0.1"
    },
    "messages": {
    "count": 0,
    "messages": []
    },
    "time": {
    "start": 1506105922.742068,
    "end": 1506105927.799333,
    "duration": 5.057265043258667,
    "duration_str": "5.06s",
    "measures": [
    {
    "label": "Booting",
    "start": 1506105922.742068,
    "relative_start": 0,
    "end": 1506105923.524004,
    "relative_end": 1506105923.524004,
    "duration": 0.7819359302520752,
    "duration_str": "781.94ms",
    "params": [],
    "collector": null
    },
    {
    "label": "Application",
    "start": 1506105923.535343,
    "relative_start": 0.7932748794555664,
    "end": 1506105927.799336,
    "relative_end": 0.00000286102294921875,
    "duration": 4.26399302482605,
    "duration_str": "4.26s",
    "params": [],
    "collector": null
    }
    ]
    },
    "memory": {
    "peak_usage": 13234248,
    "peak_usage_str": "12.62MB"
    },
    "exceptions": {
    "count": 0,
    "exceptions": []
    },
    "route": {
    "uri": "GET /",
    "middleware": "api, throttle:30,1",
    "domain": "http://api.apiato.test",
    "as": "apis_root_page",
    "controller": "App\\Containers\\Welcome\\UI\\API\\Controllers\\Controller@apiRoot",
    "namespace": "App\\Containers\\Welcome\\UI\\API\\Controllers",
    "prefix": "/",
    "where": [],
    "file": "app/Containers/Welcome/UI/API/Controllers/Controller.php:20-25"
    },
    "queries": {
    "nb_statements": 0,
    "nb_failed_statements": 0,
    "accumulated_duration": 0,
    "accumulated_duration_str": "0μs",
    "statements": []
    },
    "swiftmailer_mails": {
    "count": 0,
    "mails": []
    },
    "logs": {
    "count": 3,
    "messages": [
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694807
    },
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694811
    },
    {
    "message": "[2017-09-18 17:38:15] testing.INFO: New User registration. ID = 970ylqvaogmxnbdr | Email = apiato@mail.test. Thank you for signing up.\n</div>\n</body>\n</html>\n \n",
    "message_html": null,
    "is_string": false,
    "label": "info",
    "time": 1506105927.694812
    }
    ]
    },
    "auth": {
    "guards": {
    "web": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]",
    "api": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]"
    },
    "names": ""
    },
    "gate": {
    "count": 0,
    "messages": []
    }
    }
    }

    Configuration

    By default, the profiler feature is turned off. To turn it on edit the .env file and set DEBUGBAR_ENABLED=true.

    To control and modify the profiler response, you need to edit this config file app/Ship/Configs/debugbar.php.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/query-parameters/index.html b/docs/9.x/core-features/query-parameters/index.html index c7246a989..3a9ac8a32 100644 --- a/docs/9.x/core-features/query-parameters/index.html +++ b/docs/9.x/core-features/query-parameters/index.html @@ -4,7 +4,7 @@ Query Parameters | Apiato - + @@ -22,7 +22,7 @@ accepts driver as relationship (in the Available Relationships section).

    Nested Includes

    It is also possible to request "nested includes". Extend the example from above. Imagine, that a Driver may also have a relationship to an Address object. You can access this information as well by calling ?include=driver,driver.address.

    Of course, the address include is defined in the respective DriverTransformer that is used here.

    Usage:

    api.domain.test/endpoint?include=relationship

    Where to define the includes:

    Every Transformer can have 2 types of includes $availableIncludes and $defaultIncludes:

        protected $availableIncludes = [
    'products',
    'store',
    'recipients',
    ];

    protected $defaultIncludes = [
    'invoice',
    ];

    $defaultIncludes will not be listed in the response, only the $availableIncludes will be.

    Visit the Transformers page for more details.

    (provided by the Fractal Transformer)

    Caching skipping

    Note: You need to turn the Eloquent Query Caching ON for this feature to work. Checkout the Configuration Page "ELOQUENT_QUERY_CACHE".

    To run a new query and force disabling the cache on certain endpoints, you can use this parameter

    ?skipCache=true

    It's not recommended to keep skipping cache as it has bad impact on the performance.

    (provided by the L5 Repository)

    Configuration

    Most of these parameters are provided by the L5 Repository and configurable from the Ship/Configs/repository.php file. Some of them are built in house, or inherited from other packages such as Fractal.

    See the Query parameters from the User Developer perspective

    1) Generate the Default API documentation

    2) Visit the documentation URL

    More details in the API Docs Generator page.

    More Information

    For more details on these parameters check out these links:

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/rate-limiting/index.html b/docs/9.x/core-features/rate-limiting/index.html index ae9e22365..67a648467 100644 --- a/docs/9.x/core-features/rate-limiting/index.html +++ b/docs/9.x/core-features/rate-limiting/index.html @@ -4,14 +4,14 @@ Rate Limiting | Apiato - +
    Version: 9.x

    Rate Limiting

    apiato uses the default Laravel middleware for rate limiting (throttling).

    All REST API requests are throttled to prevent abuse and ensure stability. The exact number of calls that your application can make per day varies based on the type of request you are making.

    The rate limit window is 1 minute per endpoint, with most individual calls allowing for 30 requests in each window.

    In other words, each user is allowed to make 30 calls per endpoint every 1 minute. (For each unique access token).

    To update these values go to app/Ship/Configs/apiato.php config file, or to the ENV file.

    'throttle' => [
    'enabled' => env('API_RATE_LIMIT_ENABLED', true),
    'attempts' => env('API_RATE_LIMIT_ATTEMPTS', '30'),
    'expires' => env('API_RATE_LIMIT_EXPIRES', '1'),
    ]
    API_RATE_LIMIT_ENABLED=true
    API_RATE_LIMIT_ATTEMPTS=30
    API_RATE_LIMIT_EXPIRES=1

    For how many hits you can preform on an endpoint, you can always check the header:

    X-RateLimit-Limit →30
    X-RateLimit-Remaining →29

    Enable/Disable Rate Limiting:

    The API rate limiting middleware is enabled and applied to all the Container Endpoints by default.

    To disable it set API_RATE_LIMIT_ENABLED to false in the .env file.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/requests-monitor/index.html b/docs/9.x/core-features/requests-monitor/index.html index edc4307a6..20c564c39 100644 --- a/docs/9.x/core-features/requests-monitor/index.html +++ b/docs/9.x/core-features/requests-monitor/index.html @@ -4,13 +4,13 @@ Requests Monitor | Apiato - +
    Version: 9.x

    Requests Monitor

    Apiato provides a simple and easy way to monitor and log all the HTTP requests coming to your application.

    The request monitor can be very useful when testing and debugging your frontend Apps which consume your API. Especially when the frontend apps (Mobile, Web,...) are built by other developers who are far from you.

    The requests monitor is provided by the Debugger Container, by a RequestsMonitorMiddleware middleware.

    Enable requests logging

    From the .env file set REQUESTS_DEBUG to true.

    Now in order for this to start displaying the results you need to enable the debugging mode in Laravel by setting APP_DEBUG to true in the .env as well.

    Usage

    Simply tail the log file


    tail -f storage/logs/debugger.log

    Result

    Screenshot example:

    Change the default log file

    By default, everything is logged in the debugger.log file, to change the default file go to Debugger/Configs/debugger.php config file and set the file name:

    <?php

    /*

    |--------------------------------------------------------------------------
    | Log File
    |--------------------------------------------------------------------------
    |
    | What to name the log file in the `storage/log` path.
    |
    */

    'log_file' => 'debugger.log',

    This feature is provided by the Debugger Container via its RequestsMonitorMiddleware middleware.

    To see the results go ahead and Tail the default Laravel debug file tail -f storage/logs/laravel.log.

    Note: this will also not run in Testing environments, to enable it you need to manually edit the Middleware.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/search-query-parameter/index.html b/docs/9.x/core-features/search-query-parameter/index.html index 395252452..dacdf46ef 100644 --- a/docs/9.x/core-features/search-query-parameter/index.html +++ b/docs/9.x/core-features/search-query-parameter/index.html @@ -4,13 +4,13 @@ Search Query Parameter | Apiato - +
    Version: 9.x

    Search Query Parameter

    Below we'll see how to set up a Search Query Parameter, on a Model:

    1. Add searchable Fields on the Model Repository, all the other steps are normal steps
    <?php

    namespace App\Containers\User\Data\Repositories;

    use App\Containers\User\Contracts\UserRepositoryInterface;
    use App\Ship\Parents\Repositories\Repository;

    class UserRepository extends Repository implements UserRepositoryInterface
    {

    protected $fieldSearchable = [
    'name' => 'like',
    'id' => '=',
    'email' => '=',
    ];

    }
    1. Create basic list and search Task
    <?php

    namespace App\Containers\User\Tasks;

    use App\Containers\User\Contracts\UserRepositoryInterface;
    use App\Port\Action\Abstracts\Action;

    class ListUsersTask extends Action
    {
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    public function run($order = true)
    {
    return $this->userRepository->paginate();
    }
    }

    1. Create basic Action to call that basic Task, and maybe other Tasks later in the future when needed
    <?php

    namespace App\Containers\User\Actions;

    use App\Containers\User\Tasks\ListUsersTask;
    use App\Port\Action\Abstracts\Action;

    class ListAndSearchUsersAction extends Action
    {

    private $listUsersTask;

    public function __construct(ListUsersTask $listUsersTask)
    {
    $this->listUsersTask = $listUsersTask;
    }

    public function run($order = true)
    {
    return $this->listUsersTask->run($order);
    }
    }

    1. Use the Action from a Controller

    <?php

    public function listAllUsers()
    {
    $users = Apiato::call('User@ListAndSearchUsersAction');

    return $this->response->paginator($users, new UserTransformer());
    }

    1. Call it from anywhere as follows: [GET] http://api.apiato.com/users?search=Mahmoud@apiato.com
    - + \ No newline at end of file diff --git a/docs/9.x/core-features/social-authentication/index.html b/docs/9.x/core-features/social-authentication/index.html index 1728890c3..0bd65d78a 100644 --- a/docs/9.x/core-features/social-authentication/index.html +++ b/docs/9.x/core-features/social-authentication/index.html @@ -4,14 +4,14 @@ Social Authentication | Apiato - +
    Version: 9.x

    Social Authentication

    For Social Authentication Apiato uses Socialite.

    Default Supported Auth Provide

    • Facebook
    • Twitter

    How Social Authentication Works

    1. The Client (Mobile or Web) sends a request to the Social Auth Provider (Facebook, Twitter..).
    2. The Social Auth Provider returns a Code (Tokens).
    3. The Client makes a call to the server (our server) and passes the Code (Tokens) retrieved from the Provider.
    4. The Server fetches the user data from the Social Auth Provider using the received Code (Tokens).
    5. The Server create new User from the collected social data and return the Authenticated User (If the user already created then it just returns it).

    Setup Social Authentication

    1) Create an App on the supported Social Auth provider.

    For the callback URL you can use this Apiato web endpoint http://apiato.test/auth/{provider}/callback (replace the provider with any of the supported providers facebook, twitter,..).

    2) Set the Tokens and Secrets in the .env file

        'facebook' => [
    'client_id' => env('AUTH_FACEBOOK_CLIENT_ID'), // App ID
    'client_secret' => env('AUTH_FACEBOOK_CLIENT_SECRET'), // App Secret
    'redirect' => env('AUTH_FACEBOOK_CLIENT_REDIRECT'),
    ],

    'twitter' => [
    'client_id' => env('AUTH_TWITTER_CLIENT_ID'), // Consumer Key (API Key)
    'client_secret' => env('AUTH_TWITTER_CLIENT_SECRET'), // Consumer Secret (API Secret)
    'redirect' => env('AUTH_TWITTER_CLIENT_REDIRECT'),
    ],

    'google' => [
    'client_id' => env('AUTH_GOOGLE_CLIENT_ID'), // Client ID
    'client_secret' => env('AUTH_GOOGLE_CLIENT_SECRET'), // Client secret
    'redirect' => env('AUTH_GOOGLE_CLIENT_REDIRECT'),
    ],

    3) Make a request from your client to get the oauth info. Each Social provider returns different response and keys

    For testing purposes Apiato provides a web endpoint (http://apiato.test/auth/{provider} ) to act as a client.

    Use that endpoint from your browser (replace the provider with any of the supported providers facebook, twitter,..) to get the oauth info.

    Example Twitter Response:

    User {
    tokentoken: "121212121-121212121"
    tokentokenSecret: "34343434343434343343434343"
    tokenid: "777777777"
    tokennickname: "Mahmoud_Zalt"
    tokenname: "Mahmoud Zalt"
    tokenemail: null
    tokenavatar: "http://pbs.twimg.com/profile_images/88888888/PENrcePC_normal.jpg"
    tokenuser:
    token"avatar_original": "http://pbs.twimg.com/profile_images/9999999/PENrcePC.jpg"
    }

    NOTE: This step should be done by your client App, which could be a Web, Mobile or other kind of client Apps.

    4) Use the oauth info to make a call from your server to the Social Provider in order to get the User info.

    Example Getting Twitter User: Twitter requires the oauth_token and oauth_secret, other Providers might only require the oauth_token

    POST /v1/auth/twitter HTTP/1.1
    Host: api.apiato.test
    Content-Type: application/x-www-form-urlencoded
    Accept: application/json

    oauth_token=121212121-121212121&oauth_secret=34343434343434343343434343

    Note: For Facebook only send oauth_token which is named as access_token in facebook response. For more details about parameters checkout the generated documentation or visit app/Containers/Socialauth/UI/API/Routes/AuthenticateAll.v1.private.php

    5) The endpoint above should return the User and his Personal Access Token.

    Example Twitter Response:

    {
    "data": {
    "object": "User",
    "id": "eqwja3vw94kzmxr0",
    "name": "Mahmoud Zalt",
    "email": null,
    "confirmed": false,
    "nickname": null,
    "gender": null,
    "birth": null,
    "social_auth_provider": "twitter",
    "social_id": "42719726",
    "social_avatar": {
    "avatar": "http://pbs.twimg.com/profile_images/1111111111/PENrcePC_normal.jpg",
    "original": null
    },
    "created_at": {
    "date": "2017-10-20 21:45:03.000000",
    "timezone_type": 3,
    "timezone": "UTC"
    },
    "updated_at": {
    "date": "2017-10-20 21:45:03.000000",
    "timezone_type": 3,
    "timezone": "UTC"
    },
    "readable_created_at": "48 minutes ago",
    "readable_updated_at": "48 minutes ago"
    },
    "meta": {
    "include": [
    "roles"
    ],
    "custom": {
    "token_type": "personal",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI..."
    }
    }
    }

    Support new Auth Provider

    1) Pick an Auth Provider from the supported providers by Socialite.

    2) Go to app/Containers/Socialauth/Tasks/FindUserSocialProfileTask.php and support your provider.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/system-settings/index.html b/docs/9.x/core-features/system-settings/index.html index f32e99267..01c46ea99 100644 --- a/docs/9.x/core-features/system-settings/index.html +++ b/docs/9.x/core-features/system-settings/index.html @@ -4,13 +4,13 @@ System Settings | Apiato - +
    Version: 9.x

    System Settings

    At many cases you need to have some dynamic system settings, such as in a referral program, where you give 'gifts' to anyone who refers new users but, those gifts can be changed in the future, so it's better not have them statically created in the code, instead read from the database where an Admin can manage them at any time.

    app/Containers/Settings Container helps to store and retrieve those key values settings. It also seeds the database with the default configurations during the installation.

    Seed the default settings

    Default Settings should be seeded in app/Containers/Settings/Database/Seeders/DefaultSystemSettingsSeeder.php

    Read settings

    <?php
    $value = $this->findSettingsByKeyTask->run('whateverSettingsName');

    You can search for settings by Key as in the example above, or create a class for each settings as follows:

    <?php
    $value = $this->findWhateverSettingsTask->run();
    - + \ No newline at end of file diff --git a/docs/9.x/core-features/useful-commands/index.html b/docs/9.x/core-features/useful-commands/index.html index 6b943a642..b6fb90d54 100644 --- a/docs/9.x/core-features/useful-commands/index.html +++ b/docs/9.x/core-features/useful-commands/index.html @@ -4,7 +4,7 @@ Useful Commands | Apiato - + @@ -16,7 +16,7 @@ php artisan apiato:list:tasks

    You can also pass --withfilename flag to see all Tasks with the files names. apiato:list:tasks --withfilename

    List Container Dependencies Command

    Sometimes it is required to show dependencies between containers (e.g., how they are interlinked amongst each others). Apiato provides a command to list all dependencies for one specific container. The command does take the Apiato::call() and $this->call() (with use X) into account.

    If you want to get the dependencies for one container, you can call

    php artisan apiato:list:dependencies app/Containers/{container-name}

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/user-registration/index.html b/docs/9.x/core-features/user-registration/index.html index 78ba75d65..2c0a4820a 100644 --- a/docs/9.x/core-features/user-registration/index.html +++ b/docs/9.x/core-features/user-registration/index.html @@ -4,13 +4,13 @@ User Registration | Apiato - +
    Version: 9.x

    User Registration

    Register users by credentials (email and passwords)

    Call the http://api.apiato.test/v1/register endpoint (you can find its documentation after generating the API Docs.

    Check out the registerUser endpoint in the API Routes files.

    This will register a new User and respond with user object.

    Registration request:

    curl --request POST \
    --url http://api.apiato.test/v1/register \
    --header 'accept: application/json' \
    --header 'content-type: application/x-www-form-urlencoded' \
    --data 'email=apiato%40mail.com1&password=password&name=Mahmoud%20Zalt'

    Registration response:

    {
    "data": {
    "object": "User",
    "id": XbPW7awNkzl83LD6,
    "name": "Mahmoud Zalt",
    "email": "apiato@mail.com",
    "confirmed": null,
    "nickname": "Mega",
    "gender": "male",
    "birth": null,
    "social_auth_provider": null,
    "social_id": null,
    "social_avatar": {
    "avatar": null,
    "original": null
    },
    "created_at": "2021-03-24T15:02:56.000000Z",
    "updated_at": "2021-03-24T15:02:56.000000Z",
    "readable_created_at": "19 seconds ago",
    "readable_updated_at": "19 seconds ago"
    "roles": {
    "data": []
    }
    }
    }

    Note: After registration in order to get the user access token you will have to send another call to http://api.example.com/v1/oauth/token with following fields and values

    username => your_username e.g. admin@admin.com
    password => your_password
    grant_type => password
    client_id => your_client_id
    client_secret => your_client_secret

    For Third-Party Clients you must have client ID and secret first. You can generate them by creating new client in your app using Laravel Passport.

    For First-Party Clients you can use a proxy to add those fields on requests coming from your trusted client. For an example on how to do it look at ProxyLoginForAdminWebClientAction Action in Authentication Container.

    Register users by Social Account

    (Facebook, Twitter, Google..)

    Checkout the Social Authentication Page for how to Sign up with Social Account.

    - + \ No newline at end of file diff --git a/docs/9.x/core-features/validation/index.html b/docs/9.x/core-features/validation/index.html index baa15878b..bdcafa07d 100644 --- a/docs/9.x/core-features/validation/index.html +++ b/docs/9.x/core-features/validation/index.html @@ -4,13 +4,13 @@ Validation | Apiato - +
    Version: 9.x

    Validation

    Apiato uses the powerful Laravel validation system.

    In Apiato, validation must be defined in Request component, since every request might have different rules.

    Validation rules are automatically applied, once injecting the Request in the Controller.

    Requests help validating User data, accessibility, ownership and more can be added if needed.

    Example Request with Validation rules:

    <?php

    namespace App\Containers\User\UI\API\Requests;

    use App\Ship\Parents\Requests\Request;

    class RegisterUserRequest extends Request
    {
    /**
    * @return array
    */
    public function rules()
    {
    return [
    'email' => 'required|email|max:200|unique:users,email',
    'password' => 'required|min:20|max:300',
    'name' => 'required|min:2|max:400',
    ];
    }

    }

    Usage from Controller Example:

        public function registerUser(RegisterUserRequest $request, CreateUserAction $action)
    {
    // if the actions takes the request object, you can pass the entire request instance as parameter
    $user = Apiato::call('User@RegisterUserAction', [
    [
    $request['email'],
    $request['password'],
    $request['name'],
    $request['gender'],
    $request['birth']
    ]
    ]);

    return $this->transform($user, UserTransformer::class);
    }

    Responses

    Validation Error response format:

    Single Field:

    {
    "errors": {
    "email": [
    "The email has already been taken."
    ]
    },
    "status_code": 422,
    "message": "The given data failed to pass validation."
    }

    Multiple Fields:

    {
    "errors": {
    "email": [
    "The email field is required."
    ],
    "password": [
    "The password field is required."
    ]
    },
    "status_code": 422,
    "message": "The given data failed to pass validation."
    }

    More details about requests in the Requests Page.

    - + \ No newline at end of file diff --git a/docs/9.x/faq/index.html b/docs/9.x/faq/index.html index c564c21bc..2bf274a6c 100644 --- a/docs/9.x/faq/index.html +++ b/docs/9.x/faq/index.html @@ -4,7 +4,7 @@ Frequently Asked Questions | Apiato - + @@ -60,7 +60,7 @@ Slack.

    Lastly, if you got your question answered, consider sharing it, if you believe it can help others. You can submit a PR adding the questions and answer here on the FAQ page. Or leave it somewhere on the repository or on Slack. Thanks in advance :)

    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/conventions-and-principles/index.html b/docs/9.x/getting-started/conventions-and-principles/index.html index 0c67a68c3..5067c28d4 100644 --- a/docs/9.x/getting-started/conventions-and-principles/index.html +++ b/docs/9.x/getting-started/conventions-and-principles/index.html @@ -4,13 +4,13 @@ Conventions and Principles | Apiato - +
    Version: 9.x

    Conventions and Principles

    HTTP Methods usage in RESTful API's

    • GET (SELECT): retrieve a specific resource from the server, or a listing of resources.
    • POST (CREATE): create a new resource on the server.
    • PUT (UPDATE): update a resource on the server, providing the entire resource.
    • PATCH (UPDATE): update a resource on the server, providing only changed attributes.
    • DELETE (DELETE): remove a resource from the server.

    Naming Conventions for Routes & Actions

    • GetAllResource: to fetch all resources. You can apply ?search query parameter to filter data.
    • FindResourceByID: to search for single resource by its unique identifier.
    • CreateResource: to create a new resource.
    • UpdateResource: to update/edit existing resource.
    • DeleteResource: to delete a resource.

    General guidelines and principles for RESTful URLs

    • A URL identifies a resource.
    • URLs should include nouns, not verbs.
    • Use plural nouns only for consistency (no singular nouns).
    • Use HTTP verbs (GET, POST, PUT, DELETE) to operate on the collections and elements.
    • You should not need to go deeper than resource/identifier/resource.
    • Put the version number at the base of your URL, for example http://apiato.test/v1/path/to/resource.
    • If an input data changes the logic of the endpoint, it should be passed in the URL. If not can go in the header "like Auth Token".
    • Don't use query parameters to alter state.
    • Don't use mixed-case paths if you can help it; lowercase is best.
    • Don't use implementation-specific extensions in your URIs (.php, .py, .pl, etc.)
    • Limit your URI space as much as possible. And keep path segments short.
    • Don't put metadata in the body of a response that should be in a header

    Good URL examples

    • Find a single Car by its unique identifier (ID):
      • GET http://www.api.apiato.test/v1/cars/123
    • Get all Cars:
      • GET http://www.api.apiato.test/v1/cars
    • Find/Search cars by one or more fields:
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes;color:white
    • Order and Sort query result:
      • GET http://www.api.apiato.test/v1/cars?orderBy=created_at&sortedBy=desc
      • GET http://www.api.apiato.test/v1/cars?search=maker:mercedes&orderBy=created_at&sortedBy=desc
    • Specify optional fields:
      • GET http://www.api.apiato.test/v1/cars?filter=id;name;status
      • GET http://www.api.apiato.test/v1/cars/123?filter=id;name;status
    • Get all Drivers belonging to a Car:
      • GET http://www.api.apiato.test/v1/cars/123/drivers
      • GET http://www.api.apiato.test/v1/cars/123/drivers/123/addresses
    • Include Drivers objects relationship with the car response:
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers
      • GET http://www.api.apiato.test/v1/cars/123?include=drivers,owner
    • Add new Car:
      • POST http://www.api.apiato.test/v1/cars
    • Add new Driver to a Car:
      • POST http://www.api.apiato.test/v1/cars/123/drivers

    General principles for HTTP methods

    • Don't ever use GET to alter state; to prevent Googlebot from corrupting your data. And use GET as much as possible.
    • Don't use PUT unless you are updating an entire resource. And unless you can also legitimately do a GET on the same URI.
    • Don't use POST to retrieve information that is long-lived or that might be reasonable to cache.
    • Don't perform an operation that is not idempotent with PUT.
    • Use GET for things like calculations, unless your input is large, in which case use POST.
    • Use POST in preference to PUT when in doubt.
    • Use POST whenever you have to do something that feels RPC-like.
    • Use PUT for classes of resources that are larger or hierarchical.
    • Use DELETE in preference to POST to remove resources.
    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/installation/index.html b/docs/9.x/getting-started/installation/index.html index 678ff4048..ed45a49e4 100644 --- a/docs/9.x/getting-started/installation/index.html +++ b/docs/9.x/getting-started/installation/index.html @@ -4,7 +4,7 @@ Installation | Apiato - + @@ -34,7 +34,7 @@ try running this command homestead halt && homestead up --provision.

    A.3) Using anything else

    If you're not into virtualization solutions, you can set up your environment directly on your machine. Check the software's requirements list.

    C) Let's Play

    Now let's see it in action

    1.a. Open your web browser and visit:

    • http://apiato.test You should see an HTML page, with Apiato in the middle.
    • http://admin.apiato.test You should see an HTML Login page.

    1.b. Open your HTTP client and call:

    • http://api.apiato.test/ You should see a JSON response with message: "Welcome to apiato.",
    • http://api.apiato.test/v1 You should see a JSON response with message: "Welcome to apiato (API V1).",

    2) Make some HTTP calls to the API:

    To make the calls you can use Postman, HTTPIE or any other tool you prefer.

    Let's test the (user registration) endpoint http://api.apiato.test/v1/register with cURL:

    curl -X POST -H "Accept: application/json" -H "Cache-Control: no-cache" -F "email=mahmoud@zalt.me" -F "password=so-secret" -F "name=Mahmoud Zalt" "http://api.apiato.test/v1/register"

    You should get response like this:

    Access-Control-Allow-Origin → ...
    Cache-Control → ...
    Connection → keep-alive
    Content-Language → en
    Content-Type → application/json
    Date → Wed, 11 Apr 2000 22:55:88 GMT
    Server → nginx
    Transfer-Encoding → chunked
    Vary → Origin
    X-Powered-By → PHP/7.7.7
    X-RateLimit-Limit → 30
    X-RateLimit-Remaining → 29

    {
    "data": {
    "object": "User",
    "id": 77,
    "name": "Mahmoud Zalt",
    "email": "apiato@mail.com",
    "confirmed": null,
    "nickname": "Mega",
    "gender": "male",
    "birth": null,
    "social_auth_provider": null,
    "social_id": null,
    "social_avatar": {
    "avatar": null,
    "original": null
    },
    "created_at": {
    "date": "2017-04-05 16:17:26.000000",
    "timezone_type": 3,
    "timezone": "UTC"
    },
    "updated_at": {
    "date": "2017-04-05 16:17:26.000000",
    "timezone_type": 3,
    "timezone": "UTC"
    },
    "roles": {
    "data": []
    }
    }
    }
    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/markdown-features/index.html b/docs/9.x/getting-started/markdown-features/index.html index 5a14fae18..41cd3ca51 100644 --- a/docs/9.x/getting-started/markdown-features/index.html +++ b/docs/9.x/getting-started/markdown-features/index.html @@ -4,13 +4,13 @@ Markdown Features | Apiato - +
    Version: 9.x

    Markdown Features

    Docusaurus supports the Markdown syntax and has some additional features.

    Front Matter

    Markdown documents can have associated metadata at the top called Front Matter:

    ---
    id: my-doc
    title: My document title
    description: My document description
    sidebar_label: My doc
    ---

    Markdown content

    Regular Markdown links are supported using url paths or relative file paths.

    Let's see how to [Create a page].
    Let's see how to [Create a page].

    Let's see how to [Create a page].

    Markdown images

    Regular Markdown images are supported.

    Add an image at static/img/logo.png and use this Markdown declaration:

    ![Docusaurus logo](/img/logo.png)

    Docusaurus logo

    Code Blocks

    Markdown code blocks are supported with Syntax highlighting.

    ```jsx title="src/components/HelloDocusaurus.js"
    function HelloDocusaurus() {
    return (
    <h1>Hello, Docusaurus!</h1>
    )
    }
    ```
    src/components/HelloDocusaurus.js
    function HelloDocusaurus() {
    return <h1>Hello, Docusaurus!</h1>;
    }

    Admonitions

    Docusaurus has a special syntax to create admonitions and callouts:

    :::tip My tip

    Use this awesome feature option

    :::

    :::danger Take care

    This action is dangerous

    :::
    My tip

    Use this awesome feature option

    Take care

    This action is dangerous

    React components

    Thanks to MDX, you can make your doc more interactive and use React components inside Markdown:

    export const Highlight = ({children, color}) => (
    <span
    style={{
    backgroundColor: color,
    borderRadius: '2px',
    color: 'red',
    padding: '0.2rem',
    }}>
    {children}
    </span>
    );

    <Highlight color="#25c2a0">Docusaurus green</Highlight> and <Highlight color="#1877F2">Facebook blue</Highlight> are my favorite colors.
    Docusaurus green and Facebook blue are my favorite colors.
    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/overview/index.html b/docs/9.x/getting-started/overview/index.html index 282dd863f..983f0bb30 100644 --- a/docs/9.x/getting-started/overview/index.html +++ b/docs/9.x/getting-started/overview/index.html @@ -4,7 +4,7 @@ Overview | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    Overview

    The basic flow

    When an HTTP request is received, it first hits your predefined Endpoint (each endpoint live in its own Route file).

    Sample Route Endpoint

    <?php
    $router->get('hello', [
    'uses' => 'Controller@sayHello',
    ]);

    After the user makes a request to the endpoint [GET] www.api.apiato.com/v1/hello it calls the defined controller function (sayHello).

    Sample Controller Function

    <?php
    class Controller extends ApiController
    {
    public function sayHello(SayHelloRequest $request)
    {
    $helloMessage = Apiato::call(SayHelloAction::class);

    $this->json([
    $helloMessage
    ]);
    }
    }

    This function takes a Request class SayHelloRequest to automatically checks if the user has the right access to this endpoint. Only if the user has access, it proceeds to the function body.

    Then the function calls an Action (SayHelloAction) to perform the business logic.

    Sample Action

    <?php
    class SayHelloAction extends Action
    {
    public function run()
    {
    return 'Hello World!';
    }
    }

    The Action can do anything then return a result (could be an Object, a String or anything).

    When the Action finishes its job, the controller function gets ready to build a response.

    Json responses can be built using the helper function json ($this->json(['foo' => 'bar']);).

    Sample User Response

    [
    "Hello World!"
    ]
    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/requests/index.html b/docs/9.x/getting-started/requests/index.html index e381e5fb5..1a346929b 100644 --- a/docs/9.x/getting-started/requests/index.html +++ b/docs/9.x/getting-started/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -17,7 +17,7 @@ you can force your users to send application/json by setting 'force-accept-header' => true, in app/Ship/Configs/apiato.php or allow them to skip it completely by setting the 'force-accept-header' => false,. By default this flag is set to false.

    Calling Endpoints

    Calling unprotected endpoint example:

    curl -X POST -H "Accept: application/json" -H "Content-Type: application/x-www-form-urlencoded; -F "email=admin@admin.com" -F "password=admin" -F "=" "http://api.domain.test/v2/register"

    Calling protected endpoint (passing Bearer Token) example:

    curl -X GET -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." -H "http://api.domain.test/v1/users"
    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/responses/index.html b/docs/9.x/getting-started/responses/index.html index 514cf68ce..4f1eda22a 100644 --- a/docs/9.x/getting-started/responses/index.html +++ b/docs/9.x/getting-started/responses/index.html @@ -4,7 +4,7 @@ Responses | Apiato - + @@ -18,7 +18,7 @@ the App\Containers\User\Models\User::class is User.

    For DataArraySerializer.

    By default, the object keyword is used as a resource key for each response, and it's set manually in each transformer, to be automated later.

    Error Responses formats

    Visit each feature, example the Authentication and there you will see how an unauthenticated response looks like, same for Authorization, Validation and so on.

    Building a Responses from the Controller:

    Checkout the Controller response builder helper functions.

    - + \ No newline at end of file diff --git a/docs/9.x/getting-started/software-architectural-patterns/index.html b/docs/9.x/getting-started/software-architectural-patterns/index.html index 5e05a1607..c90da30d6 100644 --- a/docs/9.x/getting-started/software-architectural-patterns/index.html +++ b/docs/9.x/getting-started/software-architectural-patterns/index.html @@ -4,7 +4,7 @@ Software Architectural Patterns | Apiato - + @@ -41,7 +41,7 @@ app/Containers/Application/Data/Migrations/.

    10) Create Seeds

    In Laravel, the Seeds files live in the database/migrations/ folder on the root of the project. In Apiato MVC, the Seeds files can live in that same directory or/and in this container folder app/Containers/Application/Data/Seeders/.

    More Classes

    All other classes types work the same way, you can refer to the documentation for where to place them and what they should extend. For more details you can always get in touch with us on Slack.

    How to use Apiato features

    Apiato features are all provided as Actions & Tasks classes.

    • Each Action class has single function run which does one feature only.
    • Each Task class has single function run which does one job only (a tiny piece of the business logic).

    You can use Actions/Tasks classes anyway you want:

    • Using Apiato Facade with Apiato caller style $user = \Apiato::call('Car@GetDriversAction', [$request->id]);
    • Using Apiato Facade with full class name $user = \Apiato::call(GetDriversAction::class, [$request->id]);
    • Using the helper call function with full class name $user = $this->call(GetDriversAction::class, [$request->id]);
    • Using the helper call function with Apiato caller style $user = $this->call('Car@GetDriversAction', [$request->id]);
    • Without Apiato involvement using plain PHP $user = $action = new GetDriversAction::class; $action->run($request->id);
    • Without Apiato involvement using plain Laravel IoC $user = \App::make(GetDriversAction::class)->run($request->id);

    Be creative, at the end of the day it's a class with a function.

    - + \ No newline at end of file diff --git a/docs/9.x/index.html b/docs/9.x/index.html index b82922b13..0cde92589 100644 --- a/docs/9.x/index.html +++ b/docs/9.x/index.html @@ -4,13 +4,13 @@ Requirements | Apiato - +
    Version: 9.x

    Requirements

    Requirements

    • GIT
    • PHP >= 7.2 (7.4 is recommended)
    • PHP Extensions:
      • OpenSSL PHP Extension
      • PDO PHP Extension
      • Mbstring PHP Extension
      • Tokenizer PHP Extension
      • BCMath PHP Extension (required when the Hash ID feature is enabled)
      • Intl Extension (required when you use the Localization Container)
    • Composer
    • Node (required for the API Docs generator feature)
    • Web Server (example: Nginx)
    • Cache Engine (example: Redis)
    • Database Engine (example: MySQL)
    • Queues Engine (example: Beanstalkd)
    - + \ No newline at end of file diff --git a/docs/9.x/main-components/actions/index.html b/docs/9.x/main-components/actions/index.html index 25691c0a3..f4d2f7153 100644 --- a/docs/9.x/main-components/actions/index.html +++ b/docs/9.x/main-components/actions/index.html @@ -4,14 +4,14 @@ Actions | Apiato - +
    Version: 9.x

    Actions

    Definition & Principles

    Read the section in the Porto SAP Documentation (#Actions).

    Rules

    • All Actions MUST extend App\Ship\Parents\Actions\Action.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Actions
    - CreateUserAction.php
    - DeleteUserAction.php
    - ...

    Code Sample

    Delete User Action:

    <?php

    namespace App\Containers\User\Actions;

    use Apiato\Core\Foundation\Facades\Apiato;
    use App\Containers\User\Models\User;
    use App\Ship\Parents\Actions\Action;

    class CreateAdminAction extends Action
    {

    /**
    * @param string $email
    * @param string $password
    * @param string $name
    * @param bool $isClient
    *
    * @return \App\Containers\User\Models\User
    */
    public function run(string $email, string $password, string $name, bool $isClient = false): User
    {
    $admin = Apiato::call('User@CreateUserByCredentialsTask', [
    $isClient,
    $email,
    $password,
    $name
    ]);

    Apiato::call('Authorization@AssignUserToRoleTask', [$admin, ['admin']]);

    return $admin;
    }
    }

    Note: instead of passing these parameters string $email, string $password, string $name, bool $isClient = false from place to another over and over. Consider using the Transporters classes (simple DTO's "Data Transfer Objects"), for more details read the Transporters Page.

    Injecting each Task in constructor and then using it below through its property is really boring, and the more Tasks you use the worse it gets. So instead you can use the function call to call whichever Task you want and then pass any parameters to it.

    The Action itself was also called using Apiato::call() which triggers the run function in it.

    Refer to the Magical Call page for more info and examples on how to use the call function.

    Same Example using the call function:

    <?php

    namespace App\Containers\User\Actions;

    use App\Containers\User\Tasks\DeleteUserTask;
    use App\Ship\Parents\Actions\Action;

    class DeleteUserAction extends Action
    {

    public function run($userId)
    {
    return Apiato::call(DeleteUserTask::class, [$userId]); // <<<<<
    }

    }

    Example: Calling multiple Tasks:

    <?php

    namespace App\Containers\Email\Actions;

    use App\Containers\Xxx\Tasks\Sample111Task;
    use App\Containers\Xxx\Tasks\Sample222Task;
    use App\Ship\Parents\Actions\Action;

    class DemoAction extends Action
    {

    public function run($xxx, $yyy, $zzz)
    {

    $foo = Apiato::call(Sample111Task::class, [$xxx, $yyy]);

    $bar = Apiato::call(Sample222Task::class, [$zzz]);

    // ...

    }

    }

    Action usage from a Controller:

     <?php
    //...

    public function deleteUser(DeleteUserRequest $request)
    {
    $user = Apiato::call(DeleteUserAction::class, [$request->xxx, $request->yyy]);

    return $this->deleted($user);
    }

    //...

    The same Action MAY be called by multiple Controllers (Web, Api, Cli).

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/controllers/index.html b/docs/9.x/main-components/controllers/index.html index d8ab3afc0..a748bb4f8 100644 --- a/docs/9.x/main-components/controllers/index.html +++ b/docs/9.x/main-components/controllers/index.html @@ -4,7 +4,7 @@ Controllers | Apiato - + @@ -13,7 +13,7 @@ This is the most useful function which you will be using in most cases.

    • First required parameter accepts data as object or Collection of objects.
    • Second required parameter is the transformer object
    • Third optional parameter take the includes that should be returned by the response, ($availableIncludes and $defaultIncludes in the transformer class).
    • Fourth optional parameter accepts metadata to be injected in the response.
    // $user is a User Object
    return $this->transform($user, UserTransformer::class);

    // $orders is a Collection of Order Objects
    return $this->transform($orders, OrderTransformer::class, ['products', 'recipients', 'store', 'invoice']);

    withMeta This function allows including metadata in the response.

    $metaData = ['total_credits', 10000];

    return $this->withMeta($metaData)->transform($receipt, ReceiptTransformer::class);

    json This function allows passing array data to be represented as json.

    return $this->json([
    'foo': 'bar'
    ]);

    Other functions

    • accepted
    • deleted
    • noContent
    • // Some functions might not be documented, so refer to the vendor/apiato/core/Traits/ResponseTrait.php and see the public functions.
    - + \ No newline at end of file diff --git a/docs/9.x/main-components/models/index.html b/docs/9.x/main-components/models/index.html index f226aea23..be90d3c09 100644 --- a/docs/9.x/main-components/models/index.html +++ b/docs/9.x/main-components/models/index.html @@ -4,13 +4,13 @@ Models | Apiato - +
    Version: 9.x

    Models

    Definition & Principles

    Read from the Porto SAP Documentation (#Models).

    Rules

    • All Models MUST extend from App\Ship\Parents\Models\Model.
    • If the name of a model differs from the Container name you have to set the $container attribute in the repository - more details.

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Models
    - User.php
    - UserId.php
    - ...

    Code Sample

    <?php

    namespace App\Containers\Demo\Models;

    use App\Ship\Parents\Models\Model;

    class Demo extends Model
    {
    protected $table = 'demos';

    protected $fillable = [
    'label',
    'user_id'
    ];

    protected $hidden = [
    'token',
    ];

    protected $casts = [
    'total_credits' => 'float',
    ];

    protected $dates = [
    'created_at',
    'updated_at',
    ];

    public function user()
    {
    return $this->belongsTo(\App\Containes\User\Models\User::class);
    }
    }

    Notice the Demo Model has a relationship with User Model, which lives in another Container.

    Casts

    The casts attribute can be used to parse any of the model's attributes to a specific type. In the code sample below we can cast total_credits to float.

    More information about the applicable cast-types can be found in the laravel eloquent-mutators documentation.

    You can place any dates inside the $dates to parse those automatically.

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/requests/index.html b/docs/9.x/main-components/requests/index.html index f64e020c6..e29738571 100644 --- a/docs/9.x/main-components/requests/index.html +++ b/docs/9.x/main-components/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -24,7 +24,7 @@ function.

    This helper, in turn, allows to "redefine" keys in the request for subsequent processing. Consider the following example request:

    {
    "data" : {
    "name" : "John Doe"
    }
    }

    Your Task to process this data, however, requests the field data.name as data.username. You can call the helper like this:

    $request->mapInput([
    'data.name' => 'data.username',
    ]);

    The resulting structure would look like this:

    {
    "data" : {
    "username" : "John Doe"
    }
    }

    Storing Data on the Request

    During the Request life-cycle you may want to store some data on the request object and pass it to other SubActions (or Tasks).

    To store some data on the request use:

    $request->keep(['someKey' => $someValue]);

    To retrieve the data back at any time during the request life-cycle use:

    $someValue = $request->retrieve('someKey')

    Unit Testing for Actions (Request)

    Since we're passing Request objects to Actions. When writing unit tests we need to create fake Request just to pass it to the Action with some fake data.

    // creating
    $request = RegisterUserRequest::injectData($data);

    Example One:

    $data = [
    'email' => 'Mahmoud@test.test',
    'name' => 'Mahmoud',
    'password' => 'so-secret',
    ];

    // create request object with some data
    $request = RegisterUserRequest::injectData($data);

    // create instance of the Action
    $action = App::make(RegisterUserAction::class)->run($request);

    // do any kind of assertions..
    $this->assertInstanceOf(User::class, $user);

    // ...

    Example Two (With Authenticated User):

    $data = [
    'store_id' => $this->encode($store->id),
    'items' => $orderItems,
    'recipient' => $receipient,
    ];

    $user = factory(User::class)->create();

    $request = MakeOrderRequest::injectData($data, $user);

    $order = App::make(MakeOrderAction::class)->run($request);

    // ...

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/routes/index.html b/docs/9.x/main-components/routes/index.html index c05a1087e..f8bf5cf57 100644 --- a/docs/9.x/main-components/routes/index.html +++ b/docs/9.x/main-components/routes/index.html @@ -4,13 +4,13 @@ Routes | Apiato - +
    Version: 9.x

    Routes

    Definition & Principles

    Read from the Porto SAP Documentation (#Routes).

    Rules

    • API Route files MUST be named according to their API's versions, exposure and functionality. Example CreateOrder.v1.public.php, FulfillOrder.v2.public.php, CancelOrder.v1.private.php...

    • Web Route files are pretty similar to API web files, but they can be named anything.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - UI
    - API
    - Routes
    - CreateItem.v1.public.php
    - DeleteItem.v1.public.php
    - CreateItem.v2.public.php
    - DeleteItem.v1.private.php
    - ApproveItem.v1.private.php
    - ...
    - WEB
    - Routes
    - main.php
    - ...

    Web Routes

    Example: Endpoint to display a Hello View in the browser

    <?php

    $router->get('/hello', [
    'uses' => 'Controller@sayHello',
    ]);

    In all the Web Routes files the $router variable is an instance of the default Laravel Router Illuminate\Routing\Router.

    API Routes

    Example: User Login API Endpoint

    <?php

    $router->post('login', [
    'uses' => 'Controller@loginUser',
    ]);

    Example: Protected List All Users API Endpoint, for an API Routes file

    <?php

    $router->get('users', [
    'uses' => 'Controller@listAllUsers',
    'middleware' => [
    'api.auth',
    ]
    ]);

    Protect your Endpoints:

    Checkout the Authorization Page.

    Difference between Public & Private routes files

    apiato has 2 types of endpoints, Public (External) mainly for third parties clients, and Private (Internal) for your own Apps. This will help to generate separate documentations for each and keep your internal API private.

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/subactions/index.html b/docs/9.x/main-components/subactions/index.html index 1804cb25c..f4a25bda2 100644 --- a/docs/9.x/main-components/subactions/index.html +++ b/docs/9.x/main-components/subactions/index.html @@ -4,13 +4,13 @@ Sub Actions | Apiato - +
    Version: 9.x

    Sub Actions

    Definition & Principles

    Read from the Porto SAP Documentation (#Sub-Actions).

    Rules

    • All SubActions MUST extend from App\Ship\Parents\Actions\SubAction.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Actions
    - ValidateAddressSubAction.php
    - BuildOrderSubAction.php
    - ...

    Code Sample

    ValidateAddressSubAction User Action:

    <?php

    namespace App\Containers\Shipment\Actions;

    use App\Containers\Recipient\Models\Recipient;
    use App\Containers\Recipient\Tasks\UpdateRecipientTask;
    use App\Containers\Shipment\Tasks\ValidateAddressWithEasyPostTask;
    use App\Containers\Shipment\Tasks\ValidateAddressWithMelissaDataTask;
    use App\Ship\Parents\Actions\SubAction;

    class ValidateAddressSubAction extends SubAction
    {
    public function run(Recipient $recipient)
    {
    $hasValidAddress = true;

    $easyPostResponse = Apiato::call(ValidateAddressWithEasyPostTask::class, [$recipient]);

    ...
    }
    }

    Every feature available for Actions, is also available in SubActions.

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/tasks/index.html b/docs/9.x/main-components/tasks/index.html index 454db2811..307226ccd 100644 --- a/docs/9.x/main-components/tasks/index.html +++ b/docs/9.x/main-components/tasks/index.html @@ -4,13 +4,13 @@ Tasks | Apiato - +
    Version: 9.x

    Tasks

    Definition & Principles

    Read from the Porto SAP Documentation (#Tasks).

    Rules

    • All Tasks MUST extend from App\Ship\Parents\Tasks\Task.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Tasks
    - ConfirmUserEmailTask.php
    - GenerateEmailConfirmationUrlTask.php
    - SendConfirmationEmailTask.php
    - ValidateConfirmationCodeTask.php
    - SetUserEmailTask.php
    - ...

    Code Sample

    Find User Task by ID:

    <?php

    namespace App\Containers\User\Tasks;

    use App\Containers\User\Contracts\UserRepositoryInterface;
    use App\Ship\Parents\Tasks\Task;
    use Exception;

    class FindUserByIdTask extends Task
    {
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    public function run($id)
    {
    try {
    $user = $this->userRepository->find($id);
    } catch (Exception $e) {
    throw new UserNotFoundException();
    }

    return $user;
    }

    }

    Tasks usage from an Action:

    <?php

    namespace App\Containers\Email\Actions;

    use App\Containers\Email\Tasks\ConfirmUserEmailTask;
    use App\Containers\Email\Tasks\ValidateConfirmationCodeTask;
    use App\Containers\User\Tasks\FindUserByIdTask;
    use App\Ship\Parents\Actions\Action;

    class ValidateUserEmailByConfirmationCodeAction extends Action
    {
    private $validateConfirmationCodeTask;

    private $findUserByIdTask;

    private $confirmUserEmailTask;

    public function __construct(
    ValidateConfirmationCodeTask $validateConfirmationCodeTask,
    FindUserByIdTask $findUserByIdTask,
    ConfirmUserEmailTask $confirmUserEmailTask
    ) {
    $this->validateConfirmationCodeTask = $validateConfirmationCodeTask;
    $this->findUserByIdTask = $findUserByIdTask;
    $this->confirmUserEmailTask = $confirmUserEmailTask;
    }

    public function run($userId, $code)
    {
    $this->validateConfirmationCodeTask->run($userId, $code);
    $user = $this->findUserByIdTask->run($userId);
    $this->confirmUserEmailTask->run($user);
    ...
    }
    }

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/transformers/index.html b/docs/9.x/main-components/transformers/index.html index 0a3f8ffa6..cdf4b2ac1 100644 --- a/docs/9.x/main-components/transformers/index.html +++ b/docs/9.x/main-components/transformers/index.html @@ -4,13 +4,13 @@ Transformers | Apiato - +
    Version: 9.x

    Transformers

    Definition & Principles

    Read from the Porto SAP Documentation (#Transformers).

    Rules

    • All API responses MUST be formatted via a Transformer.

    • Every Transformer SHOULD extend from App\Ship\Parents\Transformers\Transformer.

    • Each Transformer MUST have a transform() function.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - UI
    - API
    - Transformers
    - UserTransformer.php
    - ...

    Code Samples

    Reward Transformer with Country relation:

    <?php

    namespace App\Containers\Item\UI\API\Transformers;

    use App\Containers\Item\Models\Item;
    use App\Ship\Parents\Transformers\Transformer;

    class ItemTransformer extends Transformer
    {

    protected $availableIncludes = [
    'images',
    ];

    protected $defaultIncludes = [
    'roles',
    ];

    public function transform(Item $item)
    {
    $response = [
    'object' => 'Item',
    'id' => $item->getHashedKey(),
    'name' => $item->name,
    'description' => $item->description,
    'price' => (float)$item->price,
    'weight' => (float)$item->weight,
    'created_at' => $item->created_at,
    'updated_at' => $item->updated_at,
    ];

    // add more or modify data for Admins only
    $response = $this->ifAdmin([
    'real_id' => $user->id,
    'deleted_at' => $user->deleted_at,
    ], $response);

    return $response;
    }

    public function includeImages(Item $item)
    {
    return $this->collection($item->images, new ItemImageTransformer());
    }

    public function includeRoles(User $user)
    {
    return $this->collection($user->roles, new RoleTransformer());
    }
    }

    Usage from Controller (Single Item)

    <?php

    // getting any Model
    $user = $this->getUser();

    // building the response with the transformer of the Model
    $this->response->item($user, new UserTransformer());

    // in case of collection of data
    $this->response->collection($user, new UserTransformer());

    // in case of Array
    $this->response->array([
    'custom_field' => 'whatever',
    'email' => $user->email,
    ]);

    // more options are available

    Usage from Controller (Multiple Items/Collection)

    <?php

    // getting many Models Paginated
    $rewards = $this->getRewards();

    // building the response with the transformer of the Model
    return $this->response->paginator($rewards, new RewardTransformer());

    Relationships (include)

    Loading relationships in Transformer (calling other Transformers):

    This can be done in 2 ways:

    1. By the User, he can specify what relations to return in response.

    2. By the Developer, define what relations to include at run time.

    From Front-end

    You can request data with their relationships directly from the API call using include=tags,user but first the Transformer need to have the availableIncludes defined with their functions like this:

    <?php

    namespace App\Containers\Account\UI\API\Transformers;

    use App\Ship\Parents\Transformers\Transformer;
    use App\Containers\Account\Models\Account;
    use App\Containers\Tag\Transformers\TagTransformer;
    use App\Containers\User\Transformers\UserTransformer;

    class AccountTransformer extends Transformer
    {
    protected $availableIncludes = [
    'tags',
    'user',
    ];

    public function transform(Account $account)
    {
    return [
    'id' => (int)$account->id,
    'url' => $account->url,
    'username' => $account->username,
    'secret' => $account->secret,
    'note' => $account->note,
    ];
    }

    public function includeTags(Account $account)
    {
    // use collection with `multi` relationship
    return $this->collection($account->tags, new TagTransformer());
    }

    public function includeUser(Account $account)
    {
    // use `item` with single relationship
    return $this->item($account->user, new UserTransformer());
    }

    }

    Now to get the Tags with the response when Accounts are requested pass the ?include=tags parameter with the [GET] request.

    To get Tags with User use the comma separator: ?include=tags,user.

    From Back-end

    From the controller you can dynamically set the DefaultInclude using (setDefaultIncludes) anytime you want.

    <?php

    return $this->response->paginator($rewards, (new ProductsTransformer())->setDefaultIncludes(['tags']));

    You need to have includeTags function defined on the transformer. Look at the full examples above.

    If you want to include a relation with every response from this transformer you can define the relation directly in the transformer on ($defaultIncludes)

    <?php

    protected $availableIncludes = [
    'users',
    ];

    protected $defaultIncludes = [
    'tags',
    ];

    // ..

    You need to have includeUser and includeTags functions defined on the transformer. Look at the full examples above.

    Transformer Available helper functions:

    • user() : returns current authenticated user object.

    • ifAdmin($adminResponse, $clientResponse) : merges normal client response with the admin extra or modified results, when current authenticated user is Admin.

    For more information about the Transformers read this.

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/transporters/index.html b/docs/9.x/main-components/transporters/index.html index d8f18b547..66918b5bb 100644 --- a/docs/9.x/main-components/transporters/index.html +++ b/docs/9.x/main-components/transporters/index.html @@ -4,7 +4,7 @@ Transporters | Apiato - + @@ -14,7 +14,7 @@ you can call:

    // "simple" access via direct properties
    $name = $transporter->name;

    // complex access via method
    $username = $transporter->getInputByKey('your.nested.username.field');

    Of course, you can also "sanitize" the data, like you would have done in the Request classes by using sanitizeInput(array).

    Finally, if you need to access the original Request object, you can access it via

    $originalRequest = $transporter->request;

    Set Data

    You can set data in many ways

    $dataTransporter = new DataTransporter($request);
    $dataTransporter->bearerToken = $request->bearerToken();

    If the data is defined as required like this on the Transporter:

        protected $schema = [
    'type' => 'object',
    'properties' => [
    'email',
    'password',
    'clientId',
    'clientPassword',
    ],
    'required' => [
    'email',
    'password',
    'clientId',
    'clientPassword',
    ],
    ];

    Then can set data on the Transporter like this:

    $dataTransporter = new ProxyApiLoginTransporter(
    array_merge($request->all(), [
    'clientId' => Config::get('authentication-container.clients.web.admin.id'),
    'clientPassword' => Config::get('authentication-container.clients.web.admin.secret')
    ])
    );

    Set Instance

    Passing Objects does not work!, because the third party package cannot hydrate it. So to pass instances from a place to another on the Transporter object, you can do the following:

    $transporter = new DataTransporter();
    $transporter->setInstance("command_instance", $this);

    Warning: you can set instances, but they do not appear when calling toArray() or other similar functions, since they cannot be hydrated. See below how you can get the instance form the Transporter object.

    Get Instance:

    $console = $data->command_instance;

    Get Data

    To get all data from the Transporter you can call $data->toArray() or $data->toJson()... there are many other functions on the class.

    To get specific data just call the data name, as you would when accessing data from a Request object $data->username.

    - + \ No newline at end of file diff --git a/docs/9.x/main-components/views/index.html b/docs/9.x/main-components/views/index.html index a4580392c..949e0a858 100644 --- a/docs/9.x/main-components/views/index.html +++ b/docs/9.x/main-components/views/index.html @@ -4,13 +4,13 @@ Views | Apiato - +
    Version: 9.x

    Views

    Definition & Principles

    Read from the Porto SAP Documentation (#Views).

    Rules

    • Views SHOULD be created inside the Containers, and they will be automatically available for use in the Web Controllers.

    • All Views are namespaced as the lower case of the Container name.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - UI
    - WEB
    - Views
    - welcome.php
    - profile.php
    - ...

    Code Sample

    Welcome page View

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome</title>
    </head>
    <body>
    <div class="container">
    <div class="content">
    <div class="title">Welcome</div>
    </div>
    </div>
    </body>
    </html>

    Example: Usage From Controller

    <?php

    namespace App\Containers\Welcome\UI\WEB\Controllers;

    use App\Ship\Parents\Controllers\WebController;

    class Controller extends WebController
    {
    public function sayWelcome()
    {
    return view('just-welcome');
    }
    }

    Namespaces

    By default, all the Container Views are namespaced to the Container name.

    Example:

    If a Container named Store has View say-hello, you can access the view like this view('store::just-welcome'). If you try to access it without the namespace view('just-welcome'), it will not find your View.

    - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/container-installer/index.html b/docs/9.x/miscellaneous/container-installer/index.html index c852be0dd..f309f5817 100644 --- a/docs/9.x/miscellaneous/container-installer/index.html +++ b/docs/9.x/miscellaneous/container-installer/index.html @@ -4,7 +4,7 @@ Containers Installer | Apiato - + @@ -24,7 +24,7 @@ that allows installing / updating containers.
  • You must provide the key extra.apiato.container.name. This key indicates the name of the folder (e.g., container) when installing the package to the /app/Containers folder. In the shown example, the container would be installed to app/Containers/Foo.
  • - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/magical-call/index.html b/docs/9.x/miscellaneous/magical-call/index.html index 1479f737a..cc6397370 100644 --- a/docs/9.x/miscellaneous/magical-call/index.html +++ b/docs/9.x/miscellaneous/magical-call/index.html @@ -4,7 +4,7 @@ Magical Call | Apiato - + @@ -27,7 +27,7 @@ automatically rolled-back from the database. However, respective operations on the file system (e.g., you may also have uploaded a profile picture for this Team already that needs to be removed in this case) need to be performed manually!

    Typically, you may want to use the transactionalCall() on the Controller level!

    Use case example

    <?php

    return Apiato::call('User@ListUsersTask', [], ['ordered']);
    // can be called this way as well Apiato::call(ListUsersTask::class, [], ['ordered']);

    return Apiato::call('User@ListUsersTask', [], ['ordered', 'clients']);

    return Apiato::call('User@ListUsersTask', [], ['admins']);

    return Apiato::call('User@ListUsersTask', [], ['admins', ['roles' => ['manager', 'employee']]]);

    The ListUsersTask class

    <?php

    namespace App\Containers\User\Tasks;

    use App\Containers\User\Data\Criterias\AdminsCriteria;
    use App\Containers\User\Data\Criterias\ClientsCriteria;
    use App\Containers\User\Data\Criterias\RoleCriteria;
    use App\Containers\User\Data\Repositories\UserRepository;
    use App\Ship\Criterias\Eloquent\OrderByCreationDateDescendingCriteria;
    use App\Ship\Parents\Tasks\Task;

    class ListUsersTask extends Task
    {
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
    $this->userRepository = $userRepository;
    }

    public function run()
    {
    return $this->userRepository->paginate();
    }

    public function clients()
    {
    $this->userRepository->pushCriteria(new ClientsCriteria());
    }

    public function admins()
    {
    $this->userRepository->pushCriteria(new AdminsCriteria());
    }

    public function ordered()
    {
    $this->userRepository->pushCriteria(new OrderByCreationDateDescendingCriteria());
    }

    public function withRole($roles)
    {
    $this->userRepository->pushCriteria(new RoleCriteria($roles));
    }

    }

    - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/postman/index.html b/docs/9.x/miscellaneous/postman/index.html index fd83188a2..ce134666c 100644 --- a/docs/9.x/miscellaneous/postman/index.html +++ b/docs/9.x/miscellaneous/postman/index.html @@ -4,7 +4,7 @@ Postman Environment | Apiato - + @@ -15,7 +15,7 @@ access all routes.

    php artisan migrate:refresh --seed
    php artisan passport:client --password
    php artisan apiato:permissions:toRole admin

    Be sure to copy and paste your new client_id and client_secret into the .env file. Like so...

    CLIENT_WEB_ADMIN_ID={CLIENT_ID}
    CLIENT_WEB_ADMIN_SECRET={CLIENT_SECRET}

    CLIENT_MOBILE_ADMIN_ID={CLIENT_ID}
    CLIENT_MOBILE_ADMIN_SECRET={CLIENT_SECRET}

    The above steps will ensure you have a functioning Apiato API environment to explore with Postman.

    Download & Install Postman

    Visit the Postman website and download the application.

    Download Postman

    Add Apiato Environment & Collection to Postman

    Download Apiato Environment & Collection

    Steps

    • Open Postman
    • Click on "import" button top left of Postman application.
    • Click on "Choose files". Select both the Environment and Collection JSON files and click add.
    • Select "Apiato Environment" from the Environment dropdown list on the top right of the Postman Application.

    Using the Postman Apiato API Collection

    The first thing you need to do to use the Apiato endpoints is to log in to your Apiato API.

    • Select the Apiato API Collection in the left menu.
    • Select Authentication folder.
    • Select Login endpoint.
    • Click Send button.

    The response will return a body with the API access token. Normally you would have to manually add this in a header with each request using Authorization: Bearer TOKEN. This however is automatically done for you.

    From this point you can now access all endpoints using the Super Admin role.

    If you would like to test logging into your application with different users then switch to the body tab on the login endpoint and update the credentials.

    {
    "email": "admin@admin.com",
    "password": "admin"
    }
    - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/tasks-queuing/index.html b/docs/9.x/miscellaneous/tasks-queuing/index.html index c5bd9a022..4c1e19cfc 100644 --- a/docs/9.x/miscellaneous/tasks-queuing/index.html +++ b/docs/9.x/miscellaneous/tasks-queuing/index.html @@ -4,7 +4,7 @@ Tasks Queuing | Apiato - + @@ -18,7 +18,7 @@

    More info here (docs).

    The only addition to the Laravel's queues in Apiato, is that by default, apiato detects which queue driver you are planning to use (based on the configs), to create the migration files required, in case type database is used.

    if (Config::get('queue.default') == 'database')
    {
    // do something
    }

    (refer to app/Ship/Migrations/ folder for more details).

    Beanstalkd

    In order to use Beanstalkd as your queue driver, you need to require the "pda/pheanstalk": "^3.1" package first. You can include this in any composer.json file you want.

    - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/tasks-scheduling/index.html b/docs/9.x/miscellaneous/tasks-scheduling/index.html index 5e2491979..8606a7851 100644 --- a/docs/9.x/miscellaneous/tasks-scheduling/index.html +++ b/docs/9.x/miscellaneous/tasks-scheduling/index.html @@ -4,7 +4,7 @@ Tasks Scheduling | Apiato - + @@ -18,7 +18,7 @@ See the Commands Page.

    Once you have your command ready, go to app/Ship/Kernels/ConsoleKernel.php and start adding the commands you need to schedule inside the schedule function.

    Example:

    <?php
    protected function schedule(Schedule $schedule)
    {
    $schedule->command('apiato:welcome')->everyMinute();
    $schedule->job(new myJob)->hourly();
    $schedule->exec('touch me.txt')->dailyAt('12:00');
    // ...
    }

    More details here.

    NOTE: you do not need to register the commands with the $commands property or point to them in the commands() function. Apiato will do that automatically for you.

    - + \ No newline at end of file diff --git a/docs/9.x/miscellaneous/tests-helpers/index.html b/docs/9.x/miscellaneous/tests-helpers/index.html index c77b06113..e1b0c4bb8 100644 --- a/docs/9.x/miscellaneous/tests-helpers/index.html +++ b/docs/9.x/miscellaneous/tests-helpers/index.html @@ -4,7 +4,7 @@ Tests Helpers | Apiato - + @@ -25,7 +25,7 @@ testing data.

    1. Go to Seeder/SeedTestingData.php seeder class, and create your live testing data.

    2. Run this command php artisan apiato:seed-test

    Debugging with PsySH

    For better debugging and development, you can open a runtime developer console while executing your test.

    Using PsySH (interactive debugger and REPL "read-eval-print loop" for PHP). The package is required by the Laravel Tinker Package.

    To use it set the breakpoint eval(\Psy\sh()); anywhere you want in any Actions, Controllers, Tasks... and run your test normally.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/commands/index.html b/docs/9.x/optional-components/commands/index.html index 91c32ebb4..78a46e84f 100644 --- a/docs/9.x/optional-components/commands/index.html +++ b/docs/9.x/optional-components/commands/index.html @@ -4,13 +4,13 @@ Commands | Apiato - +
    Version: 9.x

    Commands

    Definition

    Commands:

    • is a laravel artisan command. Laravel has its own default commands, and you create your own as well.
    • provides a way to interact with the Laravel app.
    • a Command can be scheduled by a Task scheduler, like Cron Job or by the Laravel built-in wrapper of the Cron Job "laravel scheduler".
    • Commands could be Closure based or Classes.
    • "dispatch" is the term that is usually used to call a Command.

    Principles

    • Containers MAY or MAY NOT have one or more Commands.

    • Every Command SHOULD call an Action to perform its job, and should not container any business logic.

    • Ship may contain Application general Commands.

    Rules

    • All Commands MUST extend from App\Ship\Parents\Commands\ConsoleCommand.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - UI
    - CLI
    - Commands
    - SayHelloCommand.php
    - ...
    - Ship
    - Commands
    - GeneralCommand.php
    - ...

    Code Samples

    Example: a simple Command

    <?php

    namespace App\Containers\Welcome\UI\CLI\Commands;

    use App\Ship\Parents\Commands\ConsoleCommand;

    /**
    * Class SayWelcomeCommand
    *
    * @author Mahmoud Zalt <mahmoud@zalt.me>
    */
    class SayWelcomeCommand extends ConsoleCommand
    {

    /**
    * The name and signature of the console command.
    *
    * @var string
    */
    protected $signature = 'apiato:welcome';

    /**
    * The console command description.
    *
    * @var string
    */
    protected $description = 'Just saying Welcome.';

    /**
    * Create a new command instance.
    *
    * @return void
    */
    public function __construct()
    {
    parent::__construct();
    }

    /**
    * Execute the console command.
    *
    * @return void
    */
    public function handle()
    {
    $this->info('Welcome to apiato :)'); // green color
    // $this->line('Welcome to apiato :)'); // normal color
    }
    }

    Usage from CLI (Terminal):

    php artisan apiato:welcome

    Schedule Commands Execution

    To Schedule the execution of a Command checkout the Tasks Scheduling page.

    Define Consoles Routes

    To define Console route go to app/Ship/Commands/Routes.php.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/configs/index.html b/docs/9.x/optional-components/configs/index.html index 30e69d05d..4f60efc12 100644 --- a/docs/9.x/optional-components/configs/index.html +++ b/docs/9.x/optional-components/configs/index.html @@ -4,13 +4,13 @@ Configs | Apiato - +
    Version: 9.x

    Configs

    Definition

    Configs are files that container configurations. For more details about them check this doc.

    In each Apiato container, there are two types of config files:

    • the container specific config file (a config file that contains the container specific configurations).
    • the container third party packages config files (a config file that belongs to a third party package, required by the composer file of the container).

    Principles

    • Your custom config files and third party packages config files, should be placed in the Container, unless it's too generic then it can be placed on the Ship Layer.
    • Container can have as many config files as it needs.

    Rules

    • When publishing a third party package config file move it manually to its container or to the Ship Features Config folder in case it is generic.
    • Framework config files (provided by laravel) lives at the default config directory on the root of the project.
    • You SHOULD NOT add any config file to the config directory.
    • The container specific config file, MUST have the same name of the container in lower letters and post-fixed with -container, to prevent conflicts between third party packages and container specific packages.

    Folder Structure

    - app
    - Containers
    - {container-name}
    - Configs
    - {container-name}-container.php
    - package-config-file1.php
    - ...
    - Ship
    - Configs
    - apiato.php
    - ...
    - config
    - app.php
    - ...

    Code Samples

    Example simple Config file

    <?php
    // app/Containers/{ContainerName}/Configs/{container-name}-container.php
    return [

    /*
    |--------------------------------------------------------------------------
    | Default Namespace
    |--------------------------------------------------------------------------
    */
    'namespace' => 'App',

    // some other config params here...

    You can access the respective configuration key like this:

    $value = Config::get('{container-name}-container.namespace');     // returns 'App'
    $value = config('{container-name}-container.namespace'); // same, but using a function

    $defaultValue = Config::get('{container-name}-container.unknown.key', 'defaultvalue'); // returns 'defaultvalue' as this key is not set!
    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/criterias/index.html b/docs/9.x/optional-components/criterias/index.html index e8aa76ee3..cd29a3a02 100644 --- a/docs/9.x/optional-components/criterias/index.html +++ b/docs/9.x/optional-components/criterias/index.html @@ -4,13 +4,13 @@ Criterias | Apiato - +
    Version: 9.x

    Criterias

    Definition

    Criterias are classes used to hold and apply query condition when retrieving data from the database through a Repository.

    Without using a Criteria class, you can add your query conditions to a Repository or to a Model as scope, but with Criterias, your query conditions can be shared across multiple Models and Repositories. It allows you to define the query condition once and use it anywhere in the App.

    Principles

    • Every Container MAY have its own Criterias. However, shared Criterias SHOULD be created in the Ship layer.

    • A Criteria MUST not contain any extra code, if it needs data, the data SHOULD be passed to it from the Actions or the Task. It SHOULD not run (call) any Task for data.

    Rules

    • All Criterias MUST extend from App\Ship\Parents\Criterias\Criteria.

    • Every Criteria SHOULD have an apply() function.

    • A simple query condition example "where user_id = $id", this can be named "This User Criteria", and used with all Models which have relations with the User Model.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Data
    - Criterias
    - ColourRedCriteria.php
    - RaceCarsCriteria.php
    - ...
    - Ship
    - Features
    - Criterias
    - Eloquent
    - CreatedTodayCriteria.php
    - NotNullCriteria.php
    - ...

    Code Samples

    Example: a shared Criteria

    <?php

    namespace App\Ship\Features\Criterias\Eloquent;

    use App\Ship\Parents\Criterias\Criteria;
    use Prettus\Repository\Contracts\RepositoryInterface as PrettusRepositoryInterface;

    class OrderByCreationDateDescendingCriteria extends Criteria
    {
    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->orderBy('created_at', 'desc');
    }
    }

    Usage from Task:

    <?php

    public function run()
    {
    $this->userRepository->pushCriteria(new OrderByCreationDateDescendingCriteria);

    $users = $this->userRepository->paginate();

    return $users;
    }

    Example: Criteria accepting data input:

    <?php

    namespace App\Ship\Features\Criterias\Eloquent;

    use App\Ship\Parents\Criterias\Criteria;
    use Prettus\Repository\Contracts\RepositoryInterface as PrettusRepositoryInterface;

    class ThisUserCriteria extends Criteria
    {

    private $userId;

    public function __construct($userId)
    {
    $this->userId = $userId;
    }

    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->where('user_id', '=', $this->userId);
    }
    }

    Example: Passing data from Task to Criteria:

    <?php

    public function run($user)
    {
    $this->accountRepository->pushCriteria(new ThisUserCriteria($user->id));

    $accounts = $this->accountRepository->paginate();

    return $accounts;
    }

    For more information about the Criteria read this.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/events/index.html b/docs/9.x/optional-components/events/index.html index 182668b9e..50706bf7c 100644 --- a/docs/9.x/optional-components/events/index.html +++ b/docs/9.x/optional-components/events/index.html @@ -4,13 +4,13 @@ Events | Apiato - +
    Version: 9.x

    Events

    Definition

    Events:

    • Events provide a simple observer implementation, allowing you to subscribe and listen for various events that occur in your application.
    • Events are classes that can be fired from anywhere in your application.
    • An event class will usually be bound to one or many Events Listeners Classes or has those Listeners registered to listen to it.
    • "fire" is the term that is usually used to call an Event.

    More details here.

    Principles

    • Events can be fired from Actions and or Tasks. It's preferable to choose one place only. (Tasks are recommended).
    • Events SHOULD be created inside the Containers. However, general Events CAN be created in the Port layer.

    Rules

    • Event classes CAN be placed inside the Containers in Events folders or on the Ship for the general Events.
    • All Events MUST extend from App\Ship\Parents\Events\Event.

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Events
    - SomethingHappenedEvent.php
    - ...
    - Listeners
    - ListenToMusicListener.php
    - ...

    - Ship
    - Events
    - GlobalStateChanged.php
    - SomethingBiiigHappenedEvent.php
    - ...

    Enabling Events

    Before you can use events you need to add the EventServiceProvider to the MainServiceProvider of the Ship (if this has not been registered so far). See example below.

    <?php

    namespace App\Containers\Car\Providers;

    class MainServiceProvider extends MainProvider
    {

    /**
    * Container Service Providers.
    *
    * @var array
    */
    public $serviceProviders = [
    EventServiceProvider::class,
    ];

    // ...
    }

    Usage

    In Laravel, you can create and register events in multiple way. Below is an example of an Event that handles itself.

    Event Class Example:

    <?php

    namespace App\Containers\User\Events;

    use App\Containers\User\Models\User;
    use App\Ship\Parents\Events\Event;
    use Illuminate\Broadcasting\PrivateChannel;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Support\Facades\Log;

    class UserRegisteredEvent extends Event implements ShouldQueue
    {
    protected $user;

    public function __construct(User $user)
    {
    $this->user = $user;
    }

    public function handle()
    {
    Log::info('New User registration. ID = ' . $this->user->getHashedKey() . ' | Email = ' . $this->user->email . '.');

    // ...
    }

    public function broadcastOn()
    {
    return new PrivateChannel('channel-name');
    }
    }

    Note: You will get more benefits creating Events Listeners for each Event.

    To do this you will need to create a custom EventServiceProvider in your container extending App\Ship\Parents\Providers\EventsProvider.

    Your custom EventServiceProvider needs to be registered in the containers MainServiceProvider as well.

    <?php

    namespace App\Containers\Car\Providers;

    use App\Ship\Parents\Providers\MainProvider;

    /**
    * Class MainServiceProvider.
    *
    * The Main Service Provider of this container, it will be automatically registered in the framework.
    */
    class MainServiceProvider extends MainProvider
    {

    /**
    * Container Service Providers.
    *
    * @var array
    */
    public $serviceProviders = [
    EventServiceProvider::class,
    ];

    Dispatch Events

    You can dispatch an Event from anywhere you want (ideally from Actions and Tasks).

    Example: Dispatching the Event class from the example above

    <?php

    // using helper function
    event(New UserEmailChangedEvent($user));

    // manually
    \App::make(\Illuminate\Contracts\Bus\Dispatcher\Dispatcher::class)->dispatch(New UserEmailChangedEvent($user));

    Queueing an Event

    Events can implement Illuminate\Contracts\Queue\ShouldQueue to be queued.

    Handling an Event

    You can handle jobs on dispatching an event.

    To do so, you need to implement one of the following interfaces:

    Apiato\Core\Abstracts\Events\Interfaces\ShouldHandleNow

    Apiato\Core\Abstracts\Events\Interfaces\ShouldHandle

    This will force you to implement the handle method and will make apiato execute the method upon dispatching the event.

    • The ShouldHandleNow Interface will make the event execute the handle method as soon as the event gets dispatched.

    • The ShouldHandle Interface will create an eventjob and execute the handle method async (through laravel jobs).

    namespace App\Containers\Example\Events;


    use Apiato\Core\Abstracts\Events\Interfaces\ShouldHandle;
    use App\Ship\Parents\Events\Event;

    class ExampleEvent extends Event implements ShouldHandle
    {
    /**
    * If ShouldHandle interface is implemented this variable
    * sets the time (in seconds or timestamp) to wait before a job is executed
    *
    * @var \DateTimeInterface|\DateInterval|int|null $jobDelay
    */
    public $jobDelay = 60;

    /**
    * If ShouldHandle interface is implemented this variable
    * sets the name of the queue to push the job on
    *
    * @var string $jobQueue
    */
    public $jobQueue = "example_queue";

    public function handle()
    {
    // Do some handling here
    }

    }

    Broadcasting

    Note: to define Broadcasting route go to app/Ship/Boardcasts/Routes.php.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/exceptions/index.html b/docs/9.x/optional-components/exceptions/index.html index 971945822..88a0e8b27 100644 --- a/docs/9.x/optional-components/exceptions/index.html +++ b/docs/9.x/optional-components/exceptions/index.html @@ -4,13 +4,13 @@ Exceptions | Apiato - +
    Version: 9.x

    Exceptions

    Definition

    Exceptions are classes the handles errors, and helps developers debug their code in a more efficient way.

    Principles

    • Exceptions can be thrown from anywhere in the application.
    • Exceptions SHOULD be created inside the Containers. However, general Exceptions CAN be created in the Ship layer.

    Rules

    • All Exceptions MUST extend App\Ship\Parents\Exceptions\Exception.
    • Shared (general) Exceptions between all Containers SHOULD be created in the Exceptions Ship folder (app/Ship/Exceptions/*).
    • Every Exception SHOULD have two properties httpStatusCode and message, both properties will be displayed when an error occurs. You can override those values while throwing the error.

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Exceptions
    - AccountFailedException.php
    - ...

    - Ship
    - Exceptions
    - IncorrectIdException.php
    - InternalErrorException.php
    - ...

    Code Samples

    User Exception:

    <?php

    namespace App\Containers\User\Exceptions;

    use App\Ship\Parents\Exceptions\Exception;
    use Symfony\Component\HttpFoundation\Response;

    class AccountFailedException extends Exception
    {
    public $httpStatusCode = Response::HTTP_CONFLICT;

    public $message = 'Failed creating new User.';

    public $code = 4711;
    }

    General Exception:

    <?php

    namespace App\Ship\Exceptions;

    use App\Ship\Parents\Exceptions\Exception;
    use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

    class InternalErrorException extends Exception
    {
    public $httpStatusCode = SymfonyResponse::HTTP_INTERNAL_SERVER_ERROR;

    public $message = 'Something went wrong!';
    }

    General Exception with CustomData:

    <?php

    namespace App\Ship\Exceptions;

    use App\Ship\Parents\Exceptions\Exception;
    use Symfony\Component\HttpFoundation\Response as SymfonyResponse;

    class AwesomeExceptionWithCustomData extends Exception
    {
    public $httpStatusCode = SymfonyResponse::HTTP_INTERNAL_SERVER_ERROR;

    public $message = 'Something went wrong!';

    public $code = 1234;

    /*
    * Everything you add here will be automatically added to the ExceptionFormatter on the top level!
    * You can define any structure you want or maybe include translated messages
    */
    public function addCustomData() {
    return [
    'title' => 'nice',
    'description' => 'one fancy description here',
    'foo' => true,
    'meta' => [
    'bar' => 1234,
    ]
    ];
    }
    }

    Exception usage from anywhere:

    <?php

    throw new AccountFailedException();

    Usage with Log for Debugging:

    <?php

    throw (new AccountFailedException())->debug($e); // debug() accepts string or \Exception instance

    Usage and overriding the default message:

    <?php

    throw new AccountFailedException('I am the message to be displayed for the user');

    Usage and overwriting pre-set CustomData

    <?php

    throw (new AwesomeExceptionWithCustomData())->overrideCustomData(['foo' => 'bar']);

    Application Error Codes

    Apiato provides a convenient way to manage all application error codes in one central place. Therefore, Apiato provides, amongst others, the \App\Ship\Exceptions\Codes\ApplicationErrorCodesTable class, which already holds various information for multiple errors.

    Thereby, one error look like this:

    const BASE_GENERAL_ERROR = [
    'code' => 1001,
    'title' => 'Unknown / Unspecified Error.',
    'description' => 'Something unexpected happened.',
    ];

    Note that the code is used to be sent back to the client. The title and description, however, can be used to automatically generate a documentation regarding all defined error codes and their meaning. Please note that this feature is currently not implemented but will be added later on.

    Linking Exceptions and Error Codes

    In order to link an error code to an Exception, you simply need override the useErrorCode() method of the Exception.

    Consider the following example:

    class InternalErrorException extends Exception
    {

    public $httpStatusCode = SymfonyResponse::HTTP_INTERNAL_SERVER_ERROR;

    public $message = 'Something went wrong!';

    public code = 4711; // this code will be overwritten by the useErrorCode() method!

    public function useErrorCode()
    {
    return ApplicationErrorCodes::BASE_INTERNAL_ERROR;
    }
    }

    Please note that already defined $code values may be overwritten by the useErrorCode() method! Furthermore, this feature is completely optional - you may still use the known public $code = 4711; approach to manually set an error code.

    Defining Own Error Code Tables

    Of course, Apiato allows you to define your own CustomErrorCodesTable. In fact, there already exists such a file where you can define your own error codes. Please note that the ApplicationErrorCodesTable may be adapted by Apiato - the others will not.

    If you like to split the errors in various files, you can easily create a UserErrorCodesTable in respective namespace and define the errors accordingly. However, you need to manually "register" this code table. This can be achieved in the ErrorCodeManager::getCodeTables() method.

    Now you can easily use your UserErrorCodesTable::USER_NOT_VERIFIED error in your Exception class.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/factories/index.html b/docs/9.x/optional-components/factories/index.html index 2e8858a84..1cf62953c 100644 --- a/docs/9.x/optional-components/factories/index.html +++ b/docs/9.x/optional-components/factories/index.html @@ -4,13 +4,13 @@ Factories | Apiato - +
    Version: 9.x

    Factories

    Definition

    Factories (are a short name for Models Factories).

    Factories are used to generate some fake data with the help of Faker to be used for testing purposes.

    Factories are mainly used from Tests.

    Principles

    • Factories SHOULD be created in the Containers.

    Rules

    • A Factory is just a plain PHP script. (No classes or namespaces required)

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Data
    - Factories
    - UserFactory.php
    - ...

    Code Samples

    A User Model Factory:

    <?php

    // User
    $factory->define(App\Containers\User\Models\User::class, function (Faker\Generator $faker) {
    return [
    'name' => $faker->name,
    'email' => $faker->email,
    'password' => bcrypt(str_random(10)),
    ];
    });

    // ...

    Usage from Tests or anywhere else:

    <?php

    // creating 4 users
    factory(User::class, 4)->create();

    Usage with relationships:

    <?php

    $countries = Country::all();

    // creating 3 rewards and attaching country relation to them
    $rewards = factory(Reward::class, 3)->make()->each(function ($reward) use ($countries) {
    $reward->save();
    $reward->countries()->attach([$countries->random(1)->id, $countries->random(1)->id]);
    $reward->save();
    });

    Use make instance of create and pass any data any way, then save after establishing the relations.

    Usage while overriding some values:

    <?php

    // creating single Offer and setting a user id
    $offer = factory(Offer::class)->make();
    $offer->user_id = $user->id;
    $offer->save();

    // ANOTHER EXAMPLE:

    // creating multiple Accounts
    factory(Account::class, 3)->make()->each(function ($account) use ($user) {
    $account->user_id = $user->id;
    $account->save();
    });

    For more information about the Models Factories read this.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/jobs/index.html b/docs/9.x/optional-components/jobs/index.html index a24472f56..0378f0701 100644 --- a/docs/9.x/optional-components/jobs/index.html +++ b/docs/9.x/optional-components/jobs/index.html @@ -4,13 +4,13 @@ Jobs | Apiato - +
    Version: 9.x

    Jobs

    Definition

    Jobs:

    • are simple classes that can do one thing or multiple related things.
    • Job is a name given to a class that is usually created to be queued (it's execution is usually deferred for later, after the execution of previous Jobs are completed).
    • Jobs can be scheduled to be executed later by a queuing mechanism (queue system like beanstalkd).
    • When a Job class is dispatched, it performs its specific job and dies.
    • Laravel's queue worker will process every Job as it's pushed onto the queue.

    More info here.

    Principles

    • A Container MAY have more than one Job.

    Rules

    • All Jobs MUST extend from App\Ship\Parents\Jobs\Job.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Jobs
    - DoSomethingJob.php
    - DoSomethingElseJob.php

    Code Samples

    CreateAndValidateAddress with third party Job:

    <?php

    namespace App\Containers\Shipment\Jobs;

    use App\Port\Job\Abstracts\Job;

    class CreateAndValidateAddressJob extends Job
    {
    private $recipients;

    public function __construct(array $recipients)
    {
    $this->recipients = $recipients;
    }

    public function handle()
    {
    foreach ($this->recipients as $recipient) {
    // do whatever you like
    }
    }
    }

    Check the parent Job class.

    Usage from Action:

    <?php

    // using helper function
    dispatch(new CreateAndValidateAddressJob($recipients));

    // manually
    App::make(\Illuminate\Contracts\Bus\Dispatcher\Dispatcher::class)->dispatch(New StatusChangedJob($object));

    Execute Jobs Execution

    For running your Jobs checkout the Tasks Queuing page.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/languages/index.html b/docs/9.x/optional-components/languages/index.html index c0b68f215..10abffe51 100644 --- a/docs/9.x/optional-components/languages/index.html +++ b/docs/9.x/optional-components/languages/index.html @@ -4,13 +4,13 @@ Languages | Apiato - +
    Version: 9.x

    Languages

    Definition

    Languages are not real Components, they are just files that holds translations.

    Rules

    • Languages CAN be placed inside the Containers. However, the default laravel resources/lang languages files are still loaded and can be used as well.

    • All Translations are namespaced as the lower case of the Container name.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Resources
    - Languages
    - en
    - messages.php
    - users.php
    - ar
    - messages.php
    - users.php

    Usage

    Nothing much to show here, here's how you use translated strings:

    <?php

    __('messages.welcome');

    echo __('messages.welcome');

    dd(__('messages.welcome'));

    For more info about the localization checkout the Localization page.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/mails/index.html b/docs/9.x/optional-components/mails/index.html index 309ae9166..cd3d2115a 100644 --- a/docs/9.x/optional-components/mails/index.html +++ b/docs/9.x/optional-components/mails/index.html @@ -4,13 +4,13 @@ Mails | Apiato - +
    Version: 9.x

    Mails

    Definition

    The Mail component allows you to describe an email and send it whenever needed.

    For more details refer to this link.

    Principles

    • Containers MAY or MAY NOT have one or more Mail.

    • Ship may contain general Mails.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Mails\Mail.
    • Email Templates must be placed inside the Mail directory in a Templates directory app/Containers/{container}/Mails/Templates.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Mails
    - UserRegisteredMail.php
    - ...
    - Templates
    - user-registered.blade.php
    - ...
    - Ship
    - Mails
    - SomeMail.php
    - ...
    - Templates
    - some-template.blade.php
    - ...

    Code Samples

    Example: a simple Mail

    <?php

    namespace App\Containers\User\Mails;

    use App\Containers\User\Models\User;
    use Illuminate\Bus\Queueable;
    use App\Ship\Parents\Mails\Mail;
    use Illuminate\Contracts\Queue\ShouldQueue;

    class UserRegisteredMail extends Mail implements ShouldQueue
    {
    use Queueable;

    protected $user;

    public function __construct(User $user)
    {
    $this->user = $user;
    }

    public function build()
    {
    return $this->view('user::user-registered')
    ->to($this->user->email, $this->user->name)
    ->with([
    'name' => $this->user->name,
    ]);
    }
    }

    Usage from an Action:

    Notifications can be sent from Actions or Tasks using the Mail Facade.

    Mail::send(new UserRegisteredMail($user));

    Email Templates

    Templates should be placed inside a folder Templates inside the Mail folder.

    To access a Mail template (same like accessing a web view) you must call the container name then the view name.

    In the example below we're using the user-registered.blade.php template in the User Container.

    $this->view('user::user-registered')

    Configure Emails

    Open the .env file and set the From mail and address, this will be used globally whenever the from function is not called in the Mail.

    MAIL_FROM_ADDRESS=test@test.test
    MAIL_FROM_NAME="apiato"

    To use different email address in some classes add ->to($this->email, $this->name) to the build function in your Mail class.

    By default Apiato is configured to use Log Driver MAIL_DRIVER=log, you can change that from the .env file.

    Queueing A Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/middlewares/index.html b/docs/9.x/optional-components/middlewares/index.html index e7b75a291..7fa545455 100644 --- a/docs/9.x/optional-components/middlewares/index.html +++ b/docs/9.x/optional-components/middlewares/index.html @@ -4,7 +4,7 @@ Middlewares | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    Middlewares

    Definition

    Middleware provide a convenient mechanism for filtering HTTP requests entering your application. More about them here.

    You can enable and disable Middlewares as you wish.

    Principles

    • There are two types of Middlewares, General (applied on all the Routes by default) and Endpoints Middlewares (applied on some Endpoints).

    • The Middlewares CAN be placed in Ship layer or Container layer depending on its roles.

    Rules

    • If a Middleware is written inside a Container it MUST be registered inside that Container.

    • To register Middlewares in a Container the container needs to have a MiddlewareServiceProvider, and like all other Container Providers it MUST be registered in the MainServiceProvider of that Container.

    • General Middlewares (like some default Laravel Middlewares) SHOULD live in the Ship layer app/Ship/Middlewares/* and are registered in the Ship Main Provider.

    • Third Party packages Middleware CAN be registered in Containers or on the Ship layer (wherever they make more sense). Example: the jwt.auth middleware "provided by the JWT package" is registered in the Authentication Container (Containers/Authentication/Providers/MiddlewareServiceProvider.php).

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Middlewares
    - WebAuthentication.php
    - Ship
    - Middleware
    - Http
    - EncryptCookies.php
    - VerifyCsrfToken.php

    Code Sample

    Middleware Example:

    <?php

    namespace App\Containers\Authentication\Middlewares;

    use App\Ship\Engine\Butlers\Facades\ContainersButler;
    use App\Ship\Parents\Middlewares\Middleware;
    use Closure;
    use Illuminate\Contracts\Auth\Guard;
    use Illuminate\Http\Request;

    /**
    * Class WebAuthentication
    *
    * @author Mahmoud Zalt <mahmoud@zalt.me>
    */
    class WebAuthentication extends Middleware
    {

    protected $auth;

    public function __construct(Guard $auth)
    {
    $this->auth = $auth;
    }

    public function handle(Request $request, Closure $next)
    {
    if ($this->auth->guest()) {
    return response()->view(ContainersButler::getLoginWebPageName(), [
    'errorMessage' => 'Credentials Incorrect.'
    ]);
    }

    return $next($request);
    }
    }

    Middleware registration inside the Container Example:

    <?php

    namespace App\Containers\Authentication\Providers;

    use App\Containers\Authentication\Middlewares\WebAuthentication;
    use App\Ship\Parents\Providers\MiddlewareProvider;
    use Tymon\JWTAuth\Middleware\GetUserFromToken;
    use Tymon\JWTAuth\Middleware\RefreshToken;

    class MiddlewareServiceProvider extends MiddlewareProvider
    {

    protected $middleware = [

    ];

    protected $middlewareGroups = [
    'web' => [

    ],
    'api' => [

    ],
    ];

    protected $routeMiddleware = [
    'jwt.auth' => GetUserFromToken::class,
    'jwt.refresh' => RefreshToken::class,
    'auth:web' => WebAuthentication::class,
    ];

    public function boot()
    {
    $this->loadContainersInternalMiddlewares();
    }

    public function register()
    {

    }
    }

    Middleware registration inside the Ship layer (HTTP Kernel) Example:

    <?php

    namespace App\Ship\Kernels;

    use App\Ship\Middlewares\Http\ProcessETagHeadersMiddleware;
    use App\Ship\Middlewares\Http\ProfilerMiddleware;
    use App\Ship\Middlewares\Http\ValidateJsonContent;
    use Illuminate\Foundation\Http\Kernel as LaravelHttpKernel;

    /**
    * Class HttpKernel
    *
    * A.K.A (app/Http/Kernel.php)
    *
    * @author Mahmoud Zalt <mahmoud@zalt.me>
    */
    class HttpKernel extends LaravelHttpKernel
    {

    /**
    * The application's global HTTP middleware stack.
    *
    * These middleware are run during every request to your application.
    *
    * @var array
    */
    protected $middleware = [
    // Laravel middleware's
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Ship\Middlewares\Http\TrimStrings::class,
    \App\Ship\Middlewares\Http\TrustProxies::class,

    // CORS package middleware
    \Barryvdh\Cors\HandleCors::class,
    ];

    /**
    * The application's route middleware groups.
    *
    * @var array
    */
    protected $middlewareGroups = [
    'web' => [
    \App\Ship\Middlewares\Http\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Ship\Middlewares\Http\VerifyCsrfToken::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
    ValidateJsonContent::class,
    'bindings',
    ProcessETagHeadersMiddleware::class,
    ProfilerMiddleware::class,
    // The throttle Middleware is registered by the RoutesLoaderTrait in the Core
    ],
    ];

    /**
    * The application's route middleware.
    *
    * These middleware may be assigned to groups or used individually.
    *
    * @var array
    */
    protected $routeMiddleware = [
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    ];

    }
    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/migrations/index.html b/docs/9.x/optional-components/migrations/index.html index 2677db0ab..cb37c905c 100644 --- a/docs/9.x/optional-components/migrations/index.html +++ b/docs/9.x/optional-components/migrations/index.html @@ -4,13 +4,13 @@ Migrations | Apiato - +
    Version: 9.x

    Migrations

    Definition

    Migrations (are the short name for Database Migrations).

    Migrations are the version control of your database. They are very useful for generating and documenting the database tables.

    Principles

    • Migrations SHOULD be created inside the Containers folders.

    • Migrations will be autoloaded in the framework

    Rules

    • No need to publish the DB Migrations. Just run the artisan migrate command and Laravel will read the Migrations from the Containers.

    Folder Structure

     - app
    - Containers
    - User
    - Data
    - Migrations
    - 2200_01_01_000001_create_users_table.php
    - ...

    Code Samples

    User CreateUsersTable Migrations:


    <?php

    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;

    class CreateUsersTable extends Migration
    {
    public function up()
    {
    Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
    $table->softDeletes();
    });
    }

    public function down()
    {
    Schema::drop('users');
    }
    }

    For more information about the Database Migrations read this.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/notifications/index.html b/docs/9.x/optional-components/notifications/index.html index 4984f9a18..19fff213b 100644 --- a/docs/9.x/optional-components/notifications/index.html +++ b/docs/9.x/optional-components/notifications/index.html @@ -4,7 +4,7 @@ Notifications | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    Notifications

    Definition

    Notifications allow you to inform the user about a state changes in your application.

    The Laravel notifications supports sending notifications across a variety channels (mail, SMS, Slack, Database...).

    When using the Database channel the notifications will be stored in a database to be displayed in your client interface.

    For more details refer to this link.

    Principles

    • Containers MAY or MAY NOT have one or more Notification.

    • Ship may contain Application general Notifications.

    Rules

    • All Notifications MUST extend from App\Ship\Parents\Notifications\Notification.

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Notifications
    - UserRegisteredNotification.php
    - ...
    - Ship
    - Notifications
    - SystemFailureNotification.php
    - ...

    Code Samples

    Example: a simple Notification

    <?php

    namespace App\Containers\User\Notifications;

    use App\Containers\User\Models\User;
    use App\Ship\Parents\Notifications\Notification;
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;

    class BirthdayReminderNotification extends Notification implements ShouldQueue
    {

    use Queueable;

    protected $notificationMessage;

    public function __construct($notificationMessage)
    {
    $this->notificationMessage = $notificationMessage;
    }

    public function toArray($notifiable)
    {
    return [
    'content' => $this->notificationMessage,
    ];
    }

    public function toMail($notifiable)
    {
    // $notifiable is the object you want to notify "e.g. user"
    return (new MailMessage)
    ->subject("Hello World")
    ->line("Hi, $notifiable->name")
    ->line($this->notificationMessage);
    }

    public function toSms($notifiable)
    {
    // ...
    }

    // ...
    }

    Usage from an Action or Task:

    Notifications can be sent from Actions or Tasks using the Notification Facade.

    \Notification::send($user, new BirthdayReminderNotification($notificationMessage));

    Alternatively you can use the Illuminate\Notifications\Notifiable trait on the notifiable object "e.g. User" and then call it as follows:

    // get any user
    $user = User::firstOrCreate([
    'name' => 'Mahmoud Zalt',
    'email' => 'mail@something.com',
    'phone' => '0096123456789',
    ]);

    // call notify, found on the Notifiable trait
    $user->notify(new BirthdayReminderNotification($notificationMessage));

    Select Channels

    To select a notification channel, apiato have the app/Ship/Configs/notification.php config file where you can define the array of supported channels "e.g. SMS, Email, WebPush...", to be used for all your notifications.

    If you want to override the configuration for some notifications classes, or if you prefer to define the channels within each notification class itself, you can override the via function public function via($notifiable) in the notification class and define your channels.

    Checkout laravel notification channels for list of supported integrations.

    Queueing a Notification

    To queue a notification you should use Illuminate\Bus\Queueable and implement Illuminate\Contracts\Queue\ShouldQueue.

    Use DB channel

    Generally you need to generate the notification migration php artisan notifications:table, then run php artisan migrate, however just running the migration command will do the job, since Apiato already adds the _create_notifications_table.php in the default migrations files directory app/Ship/Migrations/.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/providers/index.html b/docs/9.x/optional-components/providers/index.html index a19b94293..0c2188605 100644 --- a/docs/9.x/optional-components/providers/index.html +++ b/docs/9.x/optional-components/providers/index.html @@ -4,7 +4,7 @@ Providers | Apiato - + @@ -17,7 +17,7 @@ In apiato those providers have been renamed and moved to the Ship Layer app/Ship/Parents/Providers/*:

    • AppServiceProvider
    • RouteServiceProvider
    • AuthServiceProvider
    • BroadcastServiceProvider
    • EventsServiceProvider

    VIP Note: you should not touch those providers, instead you have to extend them from a containers providers in order to modify them. Example: the app/Containers/Authentication/Providers/AuthProvider.php is extending the AuthServiceProvider to modify it.

    Those providers are not auto registered by default, thus writing any code there will not be available, unless you extend them. Once extended the child Provider should be registered in its Container Main Provider, which makes its parent available.

    This rule does not apply to the RouteServiceProvider since it's required by Apiato, this Provider is registered by the ApiatoProvider.

    Check How Service Providers are auto-loaded.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/repositories/index.html b/docs/9.x/optional-components/repositories/index.html index 0032fb8db..ca42ab94e 100644 --- a/docs/9.x/optional-components/repositories/index.html +++ b/docs/9.x/optional-components/repositories/index.html @@ -4,13 +4,13 @@ Repositories | Apiato - +
    Version: 9.x

    Repositories

    Definition

    The Repository classes are an implementation of the Repository Design Pattern.

    Their major roles are separating the business logic from the data (or the data access Task).

    Repositories save and retrieves Models to/from the underlying storage mechanism.

    The Repository is used to separate the logic that retrieves the data and maps it to a Model, from the business logic that acts on the Model.

    Principles

    • Every Model SHOULD have a Repository.

    • A Model SHOULD always get accessed through its Repository. (Never direct access to Model).

    Rules

    • All Repositories MUST extend from App\Ship\Parents\Repositories\Repository. Extending from this class will give access to functions like (find, create, update and much more).

    • Repository name should be same like it's model name (model: Foo -> repository: FooRepository).

    • If a Repository belongs to a Model whose name is not equal to its Container name, then the Repository must set the $container property like this: $container='ContainerName'. See an example below

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Data
    - Repositories
    - UserRepository.php
    - ...

    Code Samples

    User Repository:

    <?php

    namespace App\Containers\User\Data\Repositories;

    use App\Containers\User\Contracts\UserRepositoryInterface;
    use App\Containers\User\Models\User;
    use App\Ship\Parents\Repositories\Repository;

    class UserRepository extends Repository implements UserRepositoryInterface
    {
    protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];
    }

    Usage:

    <?php

    // paginate the data by 10
    $users = $userRepository->paginate(10);

    // search by 1 field
    $cars = $carRepository->findByField('colour', $colour);

    // searching multiple fields
    $offer = $offerRepository->findWhere([
    'offer_id' => $offer_id,
    'user_id' => $user_id,
    ])->first();

    //....

    Note: If the Repository belongs to Model with a name different from its Container name, the Repository class of that Model must set the property $container and define the Container name.

    Example:

    <?php

    namespace App\Containers\Authorization\Data\Repositories;

    use App\Ship\Parents\Repositories\Repository;

    class RoleRepository extends Repository
    {
    protected $container = 'Authorization'; // the container name. Must be set when the model has different name than the container

    protected $fieldSearchable = [

    ];

    }

    Other Properties:

    API Query Parameters Property

    To enable query parameters (?search=text,...) in your API you need to set the property $fieldSearchable on the Repository class, to instruct the querying on your model.

    Example $fieldSearchable of a Repository:

         <?php

    protected $fieldSearchable = [
    'name' => 'like',
    'email' => '=',
    ];

    Continue reading to find more about those properties and what they do.

    All other Properties

    apiato uses the andersao/l5-repository package, to provide a lot of powerful features to the repository class. such as

    <?php

    // ...

    protected $cacheMinutes = 1440; // 1 day

    protected $cacheOnly = ['all'];

    To learn more about all the properties you can use, visit the andersao/l5-repository package documentation.

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/seeders/index.html b/docs/9.x/optional-components/seeders/index.html index 8297162e4..556f2cb14 100644 --- a/docs/9.x/optional-components/seeders/index.html +++ b/docs/9.x/optional-components/seeders/index.html @@ -4,13 +4,13 @@ Seeders | Apiato - +
    Version: 9.x

    Seeders

    Definition

    Seeders (are a short name for Database Seeders).

    Seeders are classes made to seed the database with real data, this data usually should exist in the Application after the installation (Example: the default Users Roles and Permissions or the list of Countries).

    Principles

    • Seeders SHOULD be created in the Containers. (If the container is using a package that publishes a Seeder class, this class should be manually placed in the Container that make use of it. Do not reply on the package to place it on its right location).

    Rules

    • Seeders should be in the right directory inside the container to be loaded.

    • To avoid any conflict between containers seeders classes, you SHOULD always prepend the Seeders of each container with the container name. (Example: UserPermissionsSeeder, ItemPermissionsSeeder). If 2 seeders classes have the same name but live in different containers, one of them will not be loaded.

    • If you wish to order the seeding of the classes, you can just append _1, _2 to your classes.

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Data
    - Seeders
    - ContainerNameRolesSeeder_1.php
    - ContainerNamePermissionsSeeder_2.php
    - ...

    Code Samples

    Roles Seeder:

    <?php

    namespace App\Containers\Order\Data\Seeders;

    use App\Ship\Parents\Seeders\Seeder;
    use Apiato\Core\Foundation\Facades\Apiato;

    class OrderPermissionsSeeder_1 extends Seeder
    {

    public function run()
    {
    Apiato::call('Authorization@CreatePermissionTask', ['approve-reject-orders']);
    Apiato::call('Authorization@CreatePermissionTask', ['find-orders']);
    Apiato::call('Authorization@CreatePermissionTask', ['list-orders']);
    Apiato::call('Authorization@CreatePermissionTask', ['update-orders']);
    Apiato::call('Authorization@CreatePermissionTask', ['delete-orders']);

    // ...

    }
    }

    Note: Same Seeder class is allowed to contain seeding for multiple Models.

    Run the Seeders

    After registering the Seeders you can run this command:

    php artisan db:seed

    To run specific Seeder class you can specify its class in the parameter as follows:

    php artisan db:seed --class="your\single\seeder\goes-here"

    Migrate & seed at the same time

    php artisan migrate --seed

    For more information about the Database Seeders read this.

    Apiato testing seeder command

    It's useful sometimes to create a big set of testing data. apiato facilitates this task:

    1. Open app/Ship/Seeders/SeedTestingData.php and write your testing data here.
    2. Run this command any time you want this data available (example at staging servers):
    php artisan apiato:seed-test
    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/tests/index.html b/docs/9.x/optional-components/tests/index.html index 7a992fd9e..5e9d9e73a 100644 --- a/docs/9.x/optional-components/tests/index.html +++ b/docs/9.x/optional-components/tests/index.html @@ -4,13 +4,13 @@ Tests | Apiato - +
    Version: 9.x

    Tests

    Definition

    Tests classes are created to test the Application classes are working as expected.

    The two most essential Tests types for this architecture are the Unit Tests and the Functional Tests. However, Integration and Acceptance Tests can be used as well.

    Principles

    • Containers MAY be covered by all types of Tests.

    • Use Functional Tests to test Container Routes are doing what's expected from them.

    • Use Unit Tests to test Container Actions and Tasks are doing what's expected from them.

    Rules

    • All Container Tests classes SHOULD extend from a Container Internal TestCase class {container-name}/tests/TestCase.php. The container TestCase MUST extend main TestCase on Ship layer App\Ship\Parents\Tests\PhpUnit\TestCase. (Adding functions to the container TestCase allows sharing those functions between all Test classes of the Container).

    Folder Structure

     - app
    - Containers
    - {container-name}
    - Tests
    - TestCase.php // the container test case
    - Unit
    - CreateUserTest.php
    - UpdateUserTest.php
    - ...
    - UI
    - API
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - WEB
    - Tests
    - Functional
    - LoginTest.php
    - LogoutTest.php
    - ...
    - CLI
    - Tests
    - Functional
    - BackupDataTest.php
    - ...

    Code Sample

    <?php

    namespace App\Containers\User\UI\API\Tests\Functional;

    use App\Containers\{container-name}\Tests\TestCase;

    class DeleteUserTest extends TestCase
    {
    protected $endpoint = '/users';

    protected $permissions = [
    'delete-users'
    ];

    public function testDeleteExistingUser_()
    {
    // get a testing user of type admin.
    $user = $this->getLoggedInTestingAdmin();

    // send the HTTP request
    $response = $this->apiCall($this->endpoint, 'delete');

    // assert response status is correct
    $this->assertEquals($response->getStatusCode(), '202');

    // ...
    }

    }

    See the Tests Helpers Page

    - + \ No newline at end of file diff --git a/docs/9.x/optional-components/values/index.html b/docs/9.x/optional-components/values/index.html index 9b17a18bb..5946598d0 100644 --- a/docs/9.x/optional-components/values/index.html +++ b/docs/9.x/optional-components/values/index.html @@ -4,7 +4,7 @@ Values | Apiato - + @@ -12,7 +12,7 @@
    Version: 9.x

    Values

    Definition & Principles

    Values are short names for the known "Value Objects" which are simple Objects, pretty similar to Models in the concept of representing data, but they do not get stored in the DB, thus they don't have ID's. They also do not hold functionality or change any state, they just hold data.

    A Value Object is an immutable object that is defined by its encapsulated attributes. We create Value Object when we need it to represent/serve/manipulate some data (attached as attributes), and we'll kill it later when we finish using it, to recreate it again when needed.

    Rules

    • All Models MUST extend from App\Ship\Parents\Values\Value.

    Folder Structure

     - App
    - Containers
    - {container-name}
    - Values
    - Output.php
    - Region.php
    - ...

    Code Sample

    <?php

    use App\Ship\Parents\Values\Value;

    class Location extends Value
    {
    private $x = null;

    private $y = null;

    protected $resourceKey = 'locales';

    public function __construct($x, $y)
    {
    $this->x = $x;
    $this->y = $y;
    }

    public function getCoordinatesAsString()
    {
    return $this->x . ' - ' . $this->y;
    }
    }
    - + \ No newline at end of file diff --git a/docs/9.x/upgrade-guide/index.html b/docs/9.x/upgrade-guide/index.html index 0a174d972..25d82975f 100644 --- a/docs/9.x/upgrade-guide/index.html +++ b/docs/9.x/upgrade-guide/index.html @@ -4,7 +4,7 @@ Upgrade Guide | Apiato - + @@ -22,7 +22,7 @@ and the term New Project (referring to the new freshly installed Apiato 5.0).

    1) Download and install Apiato 5.0. See Application Setup.

    2) Delete the Containers directory app/Containers from the new project.

    3) Move the Containers directory app/Containers from the old project to the new project.

    4) Open this file app/Ship/composer.json in your old project and only copy the required dependencies, from the old project to the same file in the new project.

    5) Again, open the app/Ship/composer.json file in the new project, and remove the following dependencies: guzzlehttp/guzzle, prettus/l5-repository, barryvdh/laravel-cors, spatie/laravel-fractal, vinkla/hashids and johannesschobel/apiato-container-installer.

    6) Move and replace the following directories from the old project to the new project: config, public, resources, database and storage.

    7) Open config/app.php and replace App\Ship\Engine\Providers\PortoServiceProvider::class with Apiato\Core\Providers\ApiatoProvider::class.

    8) Move .gitignore, phpunit.xml and .env files, from the old project to the new project.

    9) Open the .env file on the new project and append this to it API_RATE_LIMIT_ENABLED=true.

    10) Open phpunit.xml file of the new project and delete this line from the file <file>./app/Ship/Engine/Loaders/FactoryMixer/FactoriesLoader.php</file>.

    11) If you had live testing data in your old project inside app/Ship/Seeders/Data/Testing/Seeders/TestingDataSeeder.php file, then copy that file content and past it in the new project inside app/Ship/Seeders/SeedTestingData.php. You will need to rename the class (not the file) from TestingDataSeeder to SeedTestingData, and you will need to update the namespace from namespace App\Ship\Seeders\Data\Testing\Seeders; to namespace App\Ship\Seeders;.

    12) If you ever used the HashIdTrait, you need to search and replace this namespace App\Ship\Engine\Traits\HashIdTrait with this Apiato\Core\Traits\HashIdTrait.

    13) Run composer update. If you got any error at this step, try to solve it or open an Issue.

    14) Move the .git directory from the old project to the new one. Add all changes git add . then commit git commit -m 'upgrade Apiato from 4.1 to 5.0'.

    15) Run your tests vendor/bin/phpunit.

    That's it :)

    How to manually upgrade older versions to 4.1?

    Use the Manual Upgrading Guide below.

    Manual Upgrading Guide

    These commands and examples, are compatible with the Apiato 8.0 upgrade. You can just copy/past.

    1) Checkout a new branch from your stable branch, to perform the upgrade.

    git checkout -b upgrade-apiato

    2) Configure a new remote (upstream) that points to the official Apiato repository.

    git remote add upstream https://github.com/apiato/apiato

    Verify the new upstream repository was added, by listing the current configured remote repositories.

    git remote -vv

    origin git@bitbucket.org:username/my-awesome-api.git (fetch)
    origin git@bitbucket.org:username/my-awesome-api.git (push)
    upstream git@github.com:apiato/apiato.git (fetch)
    upstream git@github.com:apiato/apiato.git (push)

    3) Checkout a new branch to hold the latest Apiato changes. This branch will be merged into your upgrade-apiato branch created above.

    git checkout -b apiato-{version}
    // Example: git checkout -b apiato-8.0

    4) Configure this branch to track an upstream specific branch.

    Replace {upstream-branch-name} with the branch name you want to upgrade to (for example 8.0).

    git fetch upstream {upstream-branch-name}
    // Example: git fetch upstream 8.0

    git branch --set-upstream-to upstream/{upstream-branch-name}
    // Example: git branch --set-upstream-to upstream/8.0

    Verify your local branch is tracking the Apiato specified upstream branch.

    git branch -vv

    apiato 77b4d945 [upstream/{upstream-branch-name}] ...
    master 77d302aa [origin/master] ...

    5) Make this branch identical to the remote upstream branch

    git reset --hard upstream/{upstream-branch-name}
    // Example: git reset --hard upstream/8.0

    Verify this branch now contains the latest changes from the upstream branch.

    git log

    6) Switch back to the upgrade-apiato branch

    git checkout upgrade-apiato

    7) Now lets merge the 2 branches. This step can be done in two ways:

    Option A: Merge all the changes together and solve the conflicts if any. (Recommended)

    You can execute the next command with different different parameters, below are 2 options to pick whatever feels safer to you. Do not execute both of them.

    A1: This will overwrite your changes with the upstream changes. (Try this first and if your tests failed then you can try the second one).

    git merge --allow-unrelated-histories --strategy-option=theirs apiato-{version}
    // Example: git merge --allow-unrelated-histories --strategy-option=theirs apiato-8.0

    A2: This will let you solve all the conflicts manually. (Can be the most secure choice, but it's time consuming as well.)

    git merge --allow-unrelated-histories apiato-{version}
    // Example: git merge --allow-unrelated-histories apiato-8.0

    Option B: Manually cherry pick the commits you likes to have:

    git log {upstream-branch-name}

    (copy each commit ID, one by one)

    git cherry-pick {commit-ID}

    (if you get any conflict solve it and keep moving)

    8) Compare your .env with the new .env-example and update it.

    9) Check everything is working. By running Composer install first then re-running your tests.

    • Read the changelog releases page.
    • You may want to update your custom containers dependencies, simply follow the composer install error outputs and bump each failing dependency. (Hint: visit each package releases page, and use the version which supports the supported version of Laravel).
    • You may need to fix the failing tests.
    composer install  &&  vendor/bin/phpunit

    10) Finally, merge the upgrade-apiato (which contains the upgraded changes) with your stable branch (could be master).

    git checkout master
    git merge upgrade-apiato

    php artisan -V

    Enjoy :)

    - + \ No newline at end of file diff --git a/docs/architecture-concepts/components/index.html b/docs/architecture-concepts/components/index.html index b42d7567d..4d15181b0 100644 --- a/docs/architecture-concepts/components/index.html +++ b/docs/architecture-concepts/components/index.html @@ -4,7 +4,7 @@ Components | Apiato - + @@ -19,7 +19,7 @@ simplifying future maintenance and facilitating modifications when needed.

    Components in Porto are categorized into two types: Main Components and Optional Components.

    Main Components

    Main Components are essential for the Container's functionality and must be used to achieve its core purpose.

    Optional Components

    Optional Components offer additional functionality that can be incorporated into the Container. Their usage is discretionary, depending on specific requirements.

    tip

    To learn more about how all this fits together, read the Request Lifecycle page.

    - + \ No newline at end of file diff --git a/docs/architecture-concepts/container/index.html b/docs/architecture-concepts/container/index.html index 1816e7252..bb840695f 100644 --- a/docs/architecture-concepts/container/index.html +++ b/docs/architecture-concepts/container/index.html @@ -4,7 +4,7 @@ Container | Apiato - + @@ -23,7 +23,7 @@ you may use the apiato:generate:container interactive command:

    php artisan apiato:generate:container

    Composer Dependencies

    To manage Composer dependencies, follow these guidelines:

    • All the Composer dependencies for a specific Container should be defined within that Container's composer.json file.
    • Dependencies related to the Ship layer should be placed in the root of the Ship layer, in a composer.json file.
    • Framework core dependencies should be defined in the project's root-level composer.json file.

    In practice, you can choose to place Composer dependencies in any of these composer.json files, and they will perform the same function. The choice of location depends on what is most relevant and convenient for your project.

    Readme

    Each Container has the option to include a readme.md file at its root, which serves to explain the Container's purpose and how to use it.

    To generate new readme files, you may use the apiato:generate:readme interactive command:

    php artisan apiato:generate:readme
    - + \ No newline at end of file diff --git a/docs/architecture-concepts/index.html b/docs/architecture-concepts/index.html index 8831ffce6..9538408c2 100644 --- a/docs/architecture-concepts/index.html +++ b/docs/architecture-concepts/index.html @@ -4,7 +4,7 @@ Architecture Concepts | Apiato - + @@ -13,7 +13,7 @@ to structure the application code.

    Investing 30 minutes in reading the Porto Documentation before getting started is highly recommended and can prove to be a valuable use of your time. The document serves as a comprehensive guide and resource for understanding the Apiato project.

    - + \ No newline at end of file diff --git a/docs/architecture-concepts/porto/index.html b/docs/architecture-concepts/porto/index.html index 8264a62af..a40c0fdaf 100644 --- a/docs/architecture-concepts/porto/index.html +++ b/docs/architecture-concepts/porto/index.html @@ -4,7 +4,7 @@ Porto | Apiato - + @@ -18,7 +18,7 @@ It can be a specific feature or a wrapper around a RESTful API resource.

    note

    A Container is allowed to depend on other Containers in the same Section.

    Ship

    The Ship layer contains the infrastructure code, which consists of shared code utilized by all Containers.

    Typical Project Structure

    app
    ├── Containers
    │ ├── Section
    │ │ └── Container
    │ │ ├── Actions
    │ │ ├── Configs
    │ │ ├── Data
    │ │ │ ├── Factories
    │ │ │ ├── Migrations
    │ │ │ ├── Repositories
    │ │ │ └── Seeders
    │ │ ├── Mails
    │ │ │ └── Templates
    │ │ ├── Middlewares
    │ │ ├── Models
    │ │ ├── Notifications
    │ │ ├── Providers
    │ │ ├── Tasks
    │ │ ├── Tests
    │ │ ├── Traits
    │ │ └── UI
    │ │ ├── API
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Routes
    │ │ │ └── Transformers
    │ │ ├── WEB
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Routes
    │ │ │ └── Views
    │ │ └── CLI
    │ │ └── Commands
    │ └── Vendor `// All installed and reusable Containers`
    │ ├── ContainerA
    │ └── ContainerB
    └── Ship `// All shared code between all Containers`
    ├── Broadcasts
    ├── Commands
    ├── Configs
    ├── Contracts
    ├── Criterias
    ├── Events
    ├── Exceptions
    ├── Generators
    ├── Helpers
    ├── Kernels
    ├── Listeners
    ├── Mails
    ├── Middlewares
    ├── Migrations
    ├── Notifications
    ├── Parents
    ├── Providers
    ├── Seeders
    └── Tests

    Default Sections

    Apiato ships with two default Sections:

    • AppSection: contains all the default Containers.
    • Vendor: contains all the installed and reusable Containers.
    tip

    The Vendor section is a special Section within the Containers layer that holds installed and reusable Containers. It serves a similar purpose as the vendor folder located at the root. Any Section is permitted to depend on the Vendor Section, allowing for the utilization of its Containers.

    Read more about the Container Installer to learn how to install Vendor Containers.

    - + \ No newline at end of file diff --git a/docs/architecture-concepts/request-lifecycle/index.html b/docs/architecture-concepts/request-lifecycle/index.html index 4e3122b21..b4a6aa9ef 100644 --- a/docs/architecture-concepts/request-lifecycle/index.html +++ b/docs/architecture-concepts/request-lifecycle/index.html @@ -4,7 +4,7 @@ Request Lifecycle | Apiato - + @@ -16,7 +16,7 @@ The Tasks can be used to execute reusable subsets of the business logic, with each Task responsible for a single portion of the main Action. The View or Transformer is used to build the response that is sent back to the User.

    Request Lifecycle Diagram

    Legend:

    • Solid Line: Mandatory dependency (always used)
    • Doted Line: Optional dependency (not always used)
    • Red Solid Border: Response generation
    • Green Dashed Border: Optional component (not always used)
    - + \ No newline at end of file diff --git a/docs/components/index.html b/docs/components/index.html index 88a879b58..a6c34b555 100644 --- a/docs/components/index.html +++ b/docs/components/index.html @@ -4,7 +4,7 @@ Components | Apiato - + @@ -13,7 +13,7 @@ "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

    info

    To learn more about Apiato components, check the Components section.

    - + \ No newline at end of file diff --git a/docs/components/main-components/actions/index.html b/docs/components/main-components/actions/index.html index f0a6216d2..906767323 100644 --- a/docs/components/main-components/actions/index.html +++ b/docs/components/main-components/actions/index.html @@ -4,7 +4,7 @@ Actions | Apiato - + @@ -20,7 +20,7 @@ However, it's important to note that not all operations may be automatically rolled back. For example, file system operations, such as uploading an image, are typically not covered by the database transaction and would need to be handled manually.

    - + \ No newline at end of file diff --git a/docs/components/main-components/controllers/index.html b/docs/components/main-components/controllers/index.html index 22adbb069..37fb12648 100644 --- a/docs/components/main-components/controllers/index.html +++ b/docs/components/main-components/controllers/index.html @@ -4,7 +4,7 @@ Controllers | Apiato - + @@ -16,7 +16,7 @@ and it MUST be used in conjunction with the transform method. This is different from the meta parameter in the transform method. This metadata will be returned directly under the meta key.

    You can use this method in conjunction with the meta parameter in the transform method.

    $metaData = ['foo' => 999, 'bar'];

    $this->withMeta($metaData)->transform($sample, SampleTransformer::class, meta: ['foo' => 'bar', 'baz' => 1]);

    // Response
    {
    "data": {},
    "meta": {
    "foo": 999,
    "0": "bar",
    "include": [...],
    "custom": {
    "foo": "bar",
    "baz": 1
    },
    "pagination": {}
    }
    }

    json

    This method allows you to pass an array of data that will be represented as JSON.

    $this->json($data)

    created

    This method allows you to return a response with a 201 status code.

    $this->created($data)

    deleted

    This method allows you to return a response with a 202 status code.

    $this->deleted($deletedModel)

    // Response
    {
    "message": "Model (1) Deleted Successfully."
    }

    accepted

    This method allows you to return a response with a 202 status code.

    $this->accepted($data)

    noContent

    This method allows you to return a response with a 204 status code.

    $this->noContent()
    - + \ No newline at end of file diff --git a/docs/components/main-components/exceptions/index.html b/docs/components/main-components/exceptions/index.html index 206764f80..642d5b352 100644 --- a/docs/components/main-components/exceptions/index.html +++ b/docs/components/main-components/exceptions/index.html @@ -4,7 +4,7 @@ Exceptions | Apiato - + @@ -13,7 +13,7 @@ Translation strings are automatically translated if the translations are found. To handle localization, you can use the Localization Container.

    // Example 1
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken']);
    // Example 2
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken', 'Another not translated message']);

    Response:

    {
    "message": "The exception error message.",
    "errors": {
    "email": [
    "The email has already been taken.",
    "Another not translated message"
    ]
    }
    }

    debug

    The debug method is used for logging error messages during debugging and development. The debug method accepts string or \Exception instance

    throw (new AccountFailedException())->debug($e);
    - + \ No newline at end of file diff --git a/docs/components/main-components/index.html b/docs/components/main-components/index.html index cde21b04a..9ddeea43e 100644 --- a/docs/components/main-components/index.html +++ b/docs/components/main-components/index.html @@ -4,13 +4,13 @@ Main Components | Apiato - + - + \ No newline at end of file diff --git a/docs/components/main-components/models/index.html b/docs/components/main-components/models/index.html index 3b8410381..2501ee716 100644 --- a/docs/components/main-components/models/index.html +++ b/docs/components/main-components/models/index.html @@ -4,7 +4,7 @@ Models | Apiato - + @@ -16,7 +16,7 @@ it is essential to incorporate the ModelTrait trait into your model. By doing so, your model will benefit from various functionalities provided by the trait, such as hash ids and other features necessary for proper integration with the framework.

    use Apiato\Core\Traits\ModelTrait;

    class Demo
    {
    use ModelTrait;
    ...
    }
    - + \ No newline at end of file diff --git a/docs/components/main-components/requests/index.html b/docs/components/main-components/requests/index.html index 46eb68e8f..33aefdcce 100644 --- a/docs/components/main-components/requests/index.html +++ b/docs/components/main-components/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -73,7 +73,7 @@ which employs the Shallow technique. This middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices.

    Please note that this feature is disabled by default. To enable it, follow these steps:

    1. Navigate to the app/Ship/Configs/apiato.php configuration file.
    2. Inside the configuration file, locate the use-etag configuration parameter.
    3. Set the use-etag parameter to true.

    Keep in mind that for this feature to function correctly, the client must include the If-None-Match HTTP header, which corresponds to the ETag value, in their request.

    - + \ No newline at end of file diff --git a/docs/components/main-components/routes/index.html b/docs/components/main-components/routes/index.html index 804a64ebe..5867ab031 100644 --- a/docs/components/main-components/routes/index.html +++ b/docs/components/main-components/routes/index.html @@ -4,7 +4,7 @@ Routes | Apiato - + @@ -18,7 +18,7 @@ Maintaining this distinction enables the generation of separate documentations for each type, ensuring that your internal API remains private and secure. This feature can be configured through the Documentation Generator package.

    Public Routes:

    • Accessible to third parties.
    • May or may not require authentication.

    Private Routes:

    • Accessible only to your own apps.
    • May or may not require authentication.
    - + \ No newline at end of file diff --git a/docs/components/main-components/subactions/index.html b/docs/components/main-components/subactions/index.html index 3dcfc0ae0..19c8e1434 100644 --- a/docs/components/main-components/subactions/index.html +++ b/docs/components/main-components/subactions/index.html @@ -4,7 +4,7 @@ Sub Actions | Apiato - + @@ -13,7 +13,7 @@ They enable Actions to share a sequence of Tasks, thus promoting reusability. Similar to how Tasks enable Actions to share specific functionalities, SubActions serve to share a predefined set of Tasks.

    All the features and capabilities available for regular Actions are also applicable to SubActions.

    To generate new SubActions you may use the apiato:generate:subaction interactive command:

    php artisan apiato:generate:subaction

    Definition & Principles

    Read Porto SAP Documentation (#Sub-Actions).

    Rules

    • All SubActions:
      • MUST be placed in the app/Containers/{Section}/{Container}/Actions directory.
      • MUST extend the App\Ship\Parents\Actions\SubAction class.
        • The parent extension SHOULD be aliased as ParentSubAction.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Actions
    ├── ValidateAddressSubAction.php
    ├── BuildOrderSubAction.php
    └── ...

    Code Example

    use ...
    use App\Ship\Parents\Actions\SubAction as ParentSubAction;

    class DemoSubAction extends ParentSubAction
    {
    public function __construct(
    private readonly DemoTask $demoTask
    ) {
    }

    public function run(array $data)
    {
    return $this->demoTask->run($data);
    }
    }
    - + \ No newline at end of file diff --git a/docs/components/main-components/tasks/index.html b/docs/components/main-components/tasks/index.html index acfd53fd8..e2974491a 100644 --- a/docs/components/main-components/tasks/index.html +++ b/docs/components/main-components/tasks/index.html @@ -4,7 +4,7 @@ Tasks | Apiato - + @@ -15,7 +15,7 @@ in encapsulating functionalities that are utilized by multiple Actions spanning various Containers within your application.

    To generate new tasks you may use the apiato:generate:task interactive command:

    php artisan apiato:generate:task

    Additionally, to retrieve a list of the existing tasks in your Apiato application, use the apiato:list:tasks command.

    php artisan apiato:list:tasks

    Definition & Principles

    Read Porto SAP Documentation (#Tasks).

    Rules

    • All Tasks:
      • MUST be placed in the app/Containers/{Section}/{Container}/Tasks directory.
      • MUST extend the App\Ship\Parents\Tasks\Task class.
        • The parent extension SHOULD be aliased as ParentTask.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Tasks
    ├── CreateResourceTask.php
    ├── DeleteResourceTask.php
    └── ...

    Code Example

    use ...
    use App\Ship\Parents\Tasks\Task as ParentTask;

    class DemoTask extends ParentTask
    {
    public function run(int $a, int $b): int
    {
    return $a + $b;
    }
    }
    - + \ No newline at end of file diff --git a/docs/components/main-components/transformers/index.html b/docs/components/main-components/transformers/index.html index f11330fdf..95e799d6c 100644 --- a/docs/components/main-components/transformers/index.html +++ b/docs/components/main-components/transformers/index.html @@ -4,7 +4,7 @@ Transformers | Apiato - + @@ -54,7 +54,7 @@ This serializer is not to everyone’s tastes, because it adds a data namespace to the output. A very basic response of the DataArraySerializer will look like this:

    {
    "data": {
    "object": "User",
    "id": "XbPW7awNkzl83LD6",
    "name": "Mohammad Alavi"
    }
    }

    The DataArraySerializer is handy because it allows space for meta data (like pagination, or totals) in both Items and Collections.

    {
    "data": [ ... ],
    "meta": {
    "include": [
    "xxx",
    "yyy"
    ],
    "custom": [],
    "pagination": {
    "total": 999,
    "count": 999,
    "per_page": 999,
    "current_page": 999,
    "total_pages": 999,
    "links": {
    "next": "http://api.apiato.test/v1/accounts?page=999"
    }
    }
    }
    }
    Further Reading

    For more detailed information, please refer to Fractal and Laravel Fractal Wrapper documentations.

    - + \ No newline at end of file diff --git a/docs/components/main-components/views/index.html b/docs/components/main-components/views/index.html index a46687ca6..622fb32f2 100644 --- a/docs/components/main-components/views/index.html +++ b/docs/components/main-components/views/index.html @@ -4,7 +4,7 @@ Views | Apiato - + @@ -17,7 +17,7 @@ such as view('welcome-page'), will result in the view not being found.

    An exception to this namespace convention is for view files located in the app/Ship/Views and app/Ship/Mails/Templates directories. These views will be namespaced using the word ship instead of the Section and Container names.

    For example, you would access such a view like this: view(ship::welcome-page).

    - + \ No newline at end of file diff --git a/docs/components/optional-components/commands/index.html b/docs/components/optional-components/commands/index.html index c32efe5a3..fa83b08ad 100644 --- a/docs/components/optional-components/commands/index.html +++ b/docs/components/optional-components/commands/index.html @@ -4,7 +4,7 @@ Commands | Apiato - + @@ -12,7 +12,7 @@
    Version: 12.x

    Commands

    Apiato commands are just Laravel Commands, and they function in the exact same way as Laravel commands. However, they come with additional rules and conventions specific to Apiato.

    Rules

    • All container-specific Commands MUST be placed in the app/Containers/{Section}/{Container}/UI/CLI/Commands directory.
    • All general Commands MUST be placed in the app/Ship/Commands directory.
    • All Commands:
      • MUST extend the App\Ship\Parents\Commands\ConsoleCommand class.
        • The parent extension SHOULD be aliased as ConsoleCommand.
      • SHOULD call an Action to perform its job, and SHOULD NOT contain any business logic.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── UI
    │ └── CLI
    │ └── Commands
    │ ├── FirstCommand.php
    │ ├── SecondCommand.php
    │ └── ...
    └── Ship
    └── Commands
    ├── FirstCommand.php
    ├── SecondCommand.php
    └── ...

    Code Example

    Commands are defined exactly as you would define them in Laravel.

    Closure Commands

    You can define your console closure commands in app/Ship/Commands/closures.php.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/configs/index.html b/docs/components/optional-components/configs/index.html index 4f47fe303..a05d5cad4 100644 --- a/docs/components/optional-components/configs/index.html +++ b/docs/components/optional-components/configs/index.html @@ -4,7 +4,7 @@ Configs | Apiato - + @@ -17,7 +17,7 @@ camelCase representation of the container's Section name, succeeded by -, and then the camelCase representation of the Container name.

    For instance, if you have a container named "MyContainer" within the "MySection" section, the configuration file would be named mySection-myContainer.php.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/events/index.html b/docs/components/optional-components/events/index.html index 2048da0fc..69f414b63 100644 --- a/docs/components/optional-components/events/index.html +++ b/docs/components/optional-components/events/index.html @@ -4,7 +4,7 @@ Events | Apiato - + @@ -17,7 +17,7 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the EventServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    EventServiceProvider::class,
    ];
    }

    In The Ship

    Registering events and listeners in the Ship can be done by adding them to the listen array in the App\Ship\Providers\EventServiceProvider class.

    Events & Listeners Registration Flow

    If you are manually registering events and listeners and wish to understand the registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ ├── Events
    │ │ ├── DemoEvent.php ────►─┐
    │ │ └── ...
    │ ├── Listeners │
    │ │ ├── DemoListener.php ─►─┤
    │ │ └── ...
    │ └── Providers ▼
    │ ├── EventServiceProvider.php ─────────►─────────┐
    │ ├── MainServiceProvider.php ◄───registered─in─◄─┘
    │ └── ...
    └── Ship
    ├── Events
    │ ├── ShipDemoEvent.php ──►─┐
    │ └── ...
    ├── Listeners │
    │ ├── ShipDemoListener.php ►┤
    │ └── ...
    └── Providers ▼
    ├── EventServiceProvider.php ─────────►─────────┐
    ├── ShipProvider.php ◄───registered─in─◄─┘
    └── ...

    The following diagram illustrates the registration flow of events and listeners in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/components/optional-components/factories/index.html b/docs/components/optional-components/factories/index.html index f84ccb9cd..22348d47b 100644 --- a/docs/components/optional-components/factories/index.html +++ b/docs/components/optional-components/factories/index.html @@ -4,7 +4,7 @@ Factories | Apiato - + @@ -22,7 +22,7 @@ if your model does not extend the App\Ship\Parents\Models\Model or the App\Ship\Parents\Models\UserModel class, it is essential to include the ModelTrait trait in your model. By doing so, Apiato will be able to locate the appropriate factory and use it for the model when needed.

    use Apiato\Core\Traits\ModelTrait;

    class Demo
    {
    use ModelTrait;
    ...
    }
    - + \ No newline at end of file diff --git a/docs/components/optional-components/helpers/index.html b/docs/components/optional-components/helpers/index.html index a01f89f37..52bd06472 100644 --- a/docs/components/optional-components/helpers/index.html +++ b/docs/components/optional-components/helpers/index.html @@ -4,13 +4,13 @@ Helpers | Apiato - +
    Version: 12.x

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Deprecation Notice

    Please be aware that Container Helpers are deprecated and will be removed in the next major release.

    Rules

    • You MAY create as many helper files as you need per container.
    • All container-specific helpers MUST be placed in the app/Containers/{Section}/{Container}/Helpers directory.
    • All general helpers MUST be placed in the app/Ship/Helpers directory.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Helpers
    │ ├── helpers.php
    │ ├── mix.php
    │ └── ...
    └── Ship
    └── Helpers
    ├── another-helper.php
    ├── and-another.php
    └── ...

    Code Example

    if (!function_exists('add')) {
    function add(int $firstNumber, int $secondNumber): int
    {
    return $firstNumber + $secondNumber;
    }
    }
    - + \ No newline at end of file diff --git a/docs/components/optional-components/index.html b/docs/components/optional-components/index.html index a84b4b112..64ffc9ba6 100644 --- a/docs/components/optional-components/index.html +++ b/docs/components/optional-components/index.html @@ -4,7 +4,7 @@ Optional Components | Apiato - + @@ -14,7 +14,7 @@ Their usage is discretionary, depending on specific requirements.

    Most of these components are just Laravel components, and they function in the exact same way as Laravel components. However, they come with additional rules and conventions specific to Apiato.

    info

    To learn more about Apiato components, check the Components section.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/jobs/index.html b/docs/components/optional-components/jobs/index.html index ec0eafef3..9ce5baff5 100644 --- a/docs/components/optional-components/jobs/index.html +++ b/docs/components/optional-components/jobs/index.html @@ -4,7 +4,7 @@ Jobs | Apiato - + @@ -12,7 +12,7 @@
    Version: 12.x

    Jobs

    Apiato jobs are just Laravel Jobs, and they function in the exact same way as Laravel jobs. However, they come with additional rules and conventions specific to Apiato.

    To generate new jobs you may use the apiato:generate:job interactive command:

    php artisan apiato:generate:job

    Rules

    • All container-specific Jobs MUST be placed in the app/Containers/{Section}/{Container}/Jobs directory.
    • All general Jobs MUST be placed in the app/Ship/Jobs directory.
    • All Jobs MUST extend the App\Ship\Parents\Jobs\Job class.
      • The parent extension SHOULD be aliased as ParentJob.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Jobs
    │ ├── FooJob.php
    │ ├── BarJob.php
    │ └── ...
    └── Ship
    └── Jobs
    ├── BazJob.php
    └── ...

    Code Example

    Jobs are defined exactly as you would define them in Laravel.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/mail/index.html b/docs/components/optional-components/mail/index.html index 1073d69be..2b0730352 100644 --- a/docs/components/optional-components/mail/index.html +++ b/docs/components/optional-components/mail/index.html @@ -4,7 +4,7 @@ Mail | Apiato - + @@ -18,7 +18,7 @@ such as view('welcome'), will result in the template not being found.

    An exception to this namespace convention is for template files located in the app/Ship/Mails/Templates directory. These templates will be namespaced using the word ship instead of the Section and Container names.

    For example, you would access such a template like this: view(ship::welcome).

    - + \ No newline at end of file diff --git a/docs/components/optional-components/middlewares/index.html b/docs/components/optional-components/middlewares/index.html index 1480a5fd6..1aa0f86ae 100644 --- a/docs/components/optional-components/middlewares/index.html +++ b/docs/components/optional-components/middlewares/index.html @@ -4,7 +4,7 @@ Middlewares | Apiato - + @@ -19,7 +19,7 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the MiddlewareServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    MiddlewareServiceProvider::class,
    ];
    }

    General Middlewares

    General middlewares must be registered in the App\Ship\Kernels\HttpKernel class.

    Third Party Middlewares

    When dealing with third-party packages that require middleware registration in the App\Ship\Kernels\HttpKernel class, you should follow these guidelines:

    • Specific Container Usage: If the package is used within a particular container, register its middleware in that container App\Containers\{Section}\{Container}\Providers\MiddlewareServiceProvider class.

    • Framework-wide Usage: If the package is generic and used throughout the entire application, you can register its middleware in the App\Ship\Kernels\HttpKernel class.

    Middleware Registration Flow

    If you want to understand the middleware registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ ├── Middlewares
    │ │ ├── DemoMiddleware.php ─►─┐
    │ │ └── ...
    │ └── Providers ▼
    │ ├── MiddlewareServiceProvider.php ─────►─────┐
    │ ├── MainServiceProvider.php ◄─registered─in─◄┘
    │ └── ...
    └── Ship
    ├── Kernels
    │ ├── HttpKernel.php ◄─registered─in─◄┐
    │ └── ...
    └── Middlewares │
    ├── AnotherMiddleware.php ─────►────┘
    └── ...

    The following diagram illustrates the registration flow of middlewares in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/components/optional-components/migrations/index.html b/docs/components/optional-components/migrations/index.html index 220826775..28e7af182 100644 --- a/docs/components/optional-components/migrations/index.html +++ b/docs/components/optional-components/migrations/index.html @@ -4,7 +4,7 @@ Migrations | Apiato - + @@ -12,7 +12,7 @@
    Version: 12.x

    Migrations

    Apiato migrations are just Laravel Migrations, and they function in the exact same way as Laravel migrations. However, they come with additional rules and conventions specific to Apiato.

    To generate new migrations you may use the apiato:generate:migration interactive command:

    php artisan apiato:generate:migration

    Rules

    • All container-specific Migrations MUST be placed in the app/Containers/{Section}/{Container}/Data/Migrations directory.
    • All general Migrations MUST be placed in the app/Ship/Migrations directory.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Data
    │ └── Migrations
    │ ├── 0000_01_01_000001_create_things_table.php
    │ └── ...
    └── Ship
    └── Migrations
    ├── 0000_02_02_000002_create_another_things_table.php
    └── ...

    Code Example

    Migrations are defined exactly as you would define them in Laravel.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/notifications/index.html b/docs/components/optional-components/notifications/index.html index 7b9ecdffc..219d919e3 100644 --- a/docs/components/optional-components/notifications/index.html +++ b/docs/components/optional-components/notifications/index.html @@ -4,7 +4,7 @@ Notifications | Apiato - + @@ -24,7 +24,7 @@ As a result, you don't need to manually generate the migration file. You can directly run the migrations using the php artisan migrate command, and the notifications table will be created for you.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/policies/index.html b/docs/components/optional-components/policies/index.html index fadd98871..57f545232 100644 --- a/docs/components/optional-components/policies/index.html +++ b/docs/components/optional-components/policies/index.html @@ -4,7 +4,7 @@ Policies | Apiato - + @@ -18,10 +18,10 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the AuthServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    AuthServiceProvider::class,
    ];
    }

    Policy Auto-Discovery

    Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies. This automatic discovery process relies on adhering to standard Apiato naming conventions for policies.

    By following the rules outlined above, you allow Apiato to automatically discover your policies.

    To summarize:

    • Policies must be stored within the app/Containers/{section}/{container}/Policies directory.
    • The policy name should mirror the corresponding model's name while appending a Policy suffix. For instance, a User model corresponds to a UserPolicy policy class.

    Policy Registration Flow

    In case you are going to register your policies manually, and don't want to use the auto-discovery feature, you may want to understand the policy registration process. -Here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    └── Containers
    └── Section
    └── Container
    ├── Policies
    │ ├── DemoPolicy.php ─►─┐
    │ └── ...
    └── Providers ▼
    ├── AuthServiceProvider.php ─────────►───────┐
    ├── MainServiceProvider.php ◄─registered─in─◄┘
    └── ...

    The following diagram illustrates the registration flow of policies in the above folder structure:

    Helper Methods

    Available in v12.2.0 and above.

    All models are equipped with the owns and isOwnedBy methods, +Here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    └── Containers
    └── Section
    └── Container
    ├── Policies
    │ ├── DemoPolicy.php ─►─┐
    │ └── ...
    └── Providers ▼
    ├── AuthServiceProvider.php ─────────►───────┐
    ├── MainServiceProvider.php ◄─registered─in─◄┘
    └── ...

    The following diagram illustrates the registration flow of policies in the above folder structure:

    Helper Methods

    Available since Core v8.7.0

    All models are equipped with the owns and isOwnedBy methods, made available through the Apiato\Core\Traits\CanOwnTrait trait. -These methods offer a convenient way to determine if a model is owned by another model or if a model owns another model.

    These methods support all types of relationships, as demonstrated below:

    // Check if a user owns a post
    $user->owns($post);

    // Check if a post is owned by a user
    $post->isOwnedBy($user);
    - +These methods offer a convenient way to determine if a model is owned by another model or if a model owns another model.

    These methods support all types of relationships, as demonstrated below:

    // Check if a user owns a post
    $user->owns($post);

    // Check if a post is owned by a user
    $post->isOwnedBy($user);
    + \ No newline at end of file diff --git a/docs/components/optional-components/repository/criterias/index.html b/docs/components/optional-components/repository/criterias/index.html index 6625a65eb..148877029 100644 --- a/docs/components/optional-components/repository/criterias/index.html +++ b/docs/components/optional-components/repository/criterias/index.html @@ -4,7 +4,7 @@ Criterias | Apiato - + @@ -23,7 +23,7 @@ This approach offers the flexibility to create query conditions once and apply them consistently anywhere in your application, enhancing code reusability and maintainability.

    Rules

    • All container-specific Criterias MUST be placed in the app/Containers/{Section}/{Container}/Data/Criterias directory.
    • All general Criterias MUST be placed in the app/Ship/Criterias directory.
    • All Criterias MUST extend the App\Ship\Parents\Criterias\Criteria class.
      • The parent extension SHOULD be aliased as ParentCriteria.
    • Every Criteria MUST have an apply method.
    • A Criteria MUST not contain any extra code. If it needs data, the data MUST be passed to it.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Data
    │ └── Criterias
    │ ├── ColourRedCriteria.php
    │ ├── RaceCarsCriteria.php
    │ └── ...
    └── Ship
    └── Criterias
    ├── CreatedTodayCriteria.php
    ├── NotNullCriteria.php
    └── ...

    Code Example

    use App\Ship\Parents\Criterias\Criteria as ParentCriteria;
    use Prettus\Repository\Contracts\RepositoryInterface as PrettusRepositoryInterface;

    class IsNullCriteria extends ParentCriteria
    {
    public function __construct(
    private readonly string $field
    ) {
    }

    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->whereNull($this->field);
    }
    }

    Applying Criteria

    A Criteria can be applied to a Repository by using the pushCriteria method.

    public function __construct(
    protected readonly UserRepository $repository
    ) {
    }

    The pushCriteria method accepts an instance of a Criteria class or a string with the Criteria class name.

    public function run()
    {
    $this->repository->pushCriteria(new IsNullCriteria('email'));
    return $this->repository->paginate();
    }

    You can also apply multiple Criterias to a Repository by using the pushCriteria method multiple times.

    public function run()
    {
    $this->repository->pushCriteria(new IsNullCriteria('email'));
    $this->repository->pushCriteria(OrderByNameCriteria::class);
    return $this->repository->paginate();
    }
    - + \ No newline at end of file diff --git a/docs/components/optional-components/repository/repositories/index.html b/docs/components/optional-components/repository/repositories/index.html index d22aa7925..ad4195194 100644 --- a/docs/components/optional-components/repository/repositories/index.html +++ b/docs/components/optional-components/repository/repositories/index.html @@ -4,7 +4,7 @@ Repositories | Apiato - + @@ -58,7 +58,7 @@ findById
    getById
    findMany

    pushCriteriaWith

    This method is a wrapper around the pushCriteria method. It accepts data to be passed to the criteria class and allows for easier testing.

    findById

    This method is a wrapper around the find method. But it returns null if the record is not found.

    getById

    This method is a wrapper around the find method. But it throws a NotFoundException if the record is not found.

    findMany

    This method is a wrapper around the find method. But it returns an empty collection if no records are found.

    - + \ No newline at end of file diff --git a/docs/components/optional-components/seeders/index.html b/docs/components/optional-components/seeders/index.html index 83a3af247..f595070a8 100644 --- a/docs/components/optional-components/seeders/index.html +++ b/docs/components/optional-components/seeders/index.html @@ -4,7 +4,7 @@ Seeders | Apiato - + @@ -25,7 +25,7 @@ You can locate this seeder class at app/Ship/Seeders/SeedDeploymentData.php. Similar to the testing seeder, the deployment seeder is not automatically loaded by Apiato. You can call this seeder and populate your database with production data by executing the following command:

    php artisan apiato:seed-deployment
    - + \ No newline at end of file diff --git a/docs/components/optional-components/service-providers/index.html b/docs/components/optional-components/service-providers/index.html index 9b61bac1f..a6de240f4 100644 --- a/docs/components/optional-components/service-providers/index.html +++ b/docs/components/optional-components/service-providers/index.html @@ -4,7 +4,7 @@ Service Providers | Apiato - + @@ -36,7 +36,7 @@ which makes it available.

    note

    Do note that the App\Ship\Parents\Providers\RouteServiceProvider is a unique case. Because it's required by Apiato, it is registered by the App\Ship\Prviders\ShipProvider and is loaded automatically.

    Service Providers Registration Flow

    If you want to understand the service provider registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ ├── ContainerA
    │ │ └── Providers
    │ │ ├── CustomServiceProvider.php ─────────►────────┐
    │ │ ├── EventServiceProvider.php ─────────►────────┤
    │ │ ├── MainServiceProvider.php ◄──registered─in─◄─┘
    │ │ └── ...
    │ └── ContainerB
    │ └── Providers
    │ ├── AnotherCustomServiceProvider.php ────────►────────┐
    │ ├── EventServiceProvider.php ────────►────────┤
    │ ├── MainServiceProvider.php ◄──registered─in─◄─┤
    │ ├── MiddlewareServiceProvider.php ────────►────────┘
    │ └── ...
    └── Ship
    └── Providers
    ├── CustomGeneralServiceProvider.php ────────►────────┐
    ├── RouteServiceProvider.php ────────►────────┤
    ├── ShipProvider.php ◄──registered─in─◄─┘
    └── ...

    The following diagram illustrates the registration flow of service providers in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/components/optional-components/tests/index.html b/docs/components/optional-components/tests/index.html index 2a94ff7ed..2174bdc87 100644 --- a/docs/components/optional-components/tests/index.html +++ b/docs/components/optional-components/tests/index.html @@ -4,7 +4,7 @@ Tests | Apiato - + @@ -23,7 +23,7 @@ These types of tests provide the most confidence that your system as a whole is functioning as intended.

    Rules

    • All container-specific tests:
      • MUST be placed in the app/Containers/{Section}/{Container}/Tests directory.
      • Functional tests:
        • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional directory.
        • API tests:
          • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional/API directory.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\Functional\ApiTestCase class.
            • MUST extend the App\Containers\{Section}\{Container}\Tests\FunctionalTestCase class.
              • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
        • CLI tests:
          • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional/CLI directory.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\Functional\CliTestCase class.
            • MUST extend the App\Containers\{Section}\{Container}\Tests\FunctionalTestCase class.
              • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
      • Unit tests:
        • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Unit directory.
        • MUST extend the App\Containers\{Section}\{Container}\Tests\UnitTestCase class.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
        • Directory structure MUST exactly match the Container's directory structure.
    • All Ship Unit tests:
      • MUST be placed in the app/Ship/Tests/Unit directory.
      • MUST extend the App\Ship\Tests\ShipTestCase class.
    • All ContainerTestCases & ShipTestCase MUST extend the App\Ship\Parents\Tests\PhpUnit\TestCase class.
      • The parent extension SHOULD be aliased as ParentTestCase.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Tests
    │ ├── Functional
    │ │ ├── API
    │ │ │ ├── CreateUserTest.php
    │ │ │ └── ...
    │ │ ├── CLI
    │ │ │ ├── CreateAdminCommandTest.php
    │ │ │ └── ...
    │ │ ├── ApiTestCase.php
    │ │ └── CliTestCase.php
    │ ├── Unit
    │ │ ├── Actions
    │ │ │ ├── CreateUserActionTest.php
    │ │ │ └── ...
    │ │ ├── AnotherDirectory
    │ │ │ ├── ...
    │ │ │ └── ...
    │ │ └── UI
    │ │ ├── API
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Transformers
    │ │ │ └── ...
    │ │ └── WEB
    │ │ ├── Controllers
    │ │ ├── Requests
    │ │ ├── Transformers
    │ │ └── ...
    │ ├── ContainerTestCase.php
    │ ├── FunctionalTestCase.php
    │ └── UnitTestCase.php
    └── Ship
    └── Tests
    ├── Unit
    │ ├── UrlRuleTest.php
    │ └── ...
    └── ShipTestCase.php

    Writing Tests

    Unit tests are defined in the same manner as you would define them in Laravel. However, Functional tests follow a distinct approach. Here's an example of how to write functional tests:

    namespace App\Containers\AppSection\User\Tests\Functional\API;

    use App\Containers\AppSection\User\Data\Factories\UserFactory;
    use App\Containers\AppSection\User\Tests\Functional\ApiTestCase;
    use Illuminate\Testing\Fluent\AssertableJson;
    use PHPUnit\Framework\Attributes\CoversNothing;
    use PHPUnit\Framework\Attributes\Group;

    #[Group('user')]
    #[CoversNothing]
    class FindUserByIdTest extends ApiTestCase
    {
    protected string $endpoint = 'get@v1/users/{id}';
    protected bool $auth = true;
    protected array $access = [
    'permissions' => 'search-users',
    'roles' => '',
    ];

    public function testFindUser(): void
    {
    $user = $this->getTestingUser();

    $response = $this->injectId($user->id)->makeCall();

    $response->assertOk();
    $response->assertJson(
    static fn (AssertableJson $json) => $json->has('data')
    ->where('data.id', \Hashids::encode($user->id))
    ->etc()
    );
    }
    }

    To learn more about the properties and methods used, -such as endpoint and makeCall, please read to the following section.

    Functional Test Helpers

    Apiato provides a set of helper methods that you can use to write expressive functional tests.

    Properties

    Certain test helper methods access properties defined in your test class to execute their tasks effectively. +such as endpoint and makeCall, please read to the following section.

    Functional Tests

    Properties

    Certain test helper methods access properties defined in your test class to execute their tasks effectively. Below, we will explore these properties and their corresponding methods:


    endpoint

    The $endpoint property is used to define the endpoints you want to access when making a call using the makeCall method. It is defined as a string in the following format: method@url.

    class FindUserByIdTest extends ApiTestCase
    {
    protected string $endpoint = 'get@v1/profile';

    public function testGetAuthenticatedUser(): void
    {
    $user = $this->getTestingUser();

    $response = $this->makeCall();
    // You can override the "endpoint" property in specific test methods
    // $response = $this->endpoint('get@v1/users')->makeCall();

    $response->assertOk();
    // other assertions...
    }
    }

    auth

    The $auth property is used to determine whether the endpoint being called requires authentication or not in your test class. @@ -77,7 +77,9 @@ specifically for a particular test method.

    $this->auth(false)->makeCall();

    Available Assertions

    Apiato provides a variety of custom assertion methods that you may utilize when testing your application.

    assertModelCastsIsEmpty
    assertDatabaseTable
    getGateMock
    -inIds


    assertModelCastsIsEmpty

    The assertModelCastsIsEmpty method allows you to assert that the $casts property of a model is empty. +assertCriteriaPushedToRepository
    +assertNoCriteriaPushedToRepository
    +allowAddRequestCriteriaInvocation


    assertModelCastsIsEmpty

    The assertModelCastsIsEmpty method allows you to assert that the $casts property of a model is empty. By default, the $casts property of a model includes the id and, if the model is soft deletable, the deleted_at. This method excludes these default values from the assertion.

    Here's an example of how to use assertModelCastsIsEmpty:

    $this->assertModelCastsIsEmpty($model);

    In the code snippet above, $model represents the instance of the model you want to test. @@ -86,11 +88,11 @@ you can pass them as an array to the assertModelCastsIsEmpty method.

    $this->assertModelCastsIsEmpty($model, ['value1', 'value2']);

    In this case, the assertion will ignore the id, deleted_at, value1, and value2 values when verifying the $casts property of the model.

    By using the assertModelCastsIsEmpty method, you can verify that the $casts property of a model does not contain any unexpected values, -ensuring that the model's attributes are not automatically casted.


    assertDatabaseTable

    Available in v12.1.0 and above.

    This method is used +ensuring that the model's attributes are not automatically casted.


    assertDatabaseTable

    Available since Core v8.5.0

    This method is used to verify if the database table specified by table has the expected columns specified in the expectedColumns array. The array should be in the format ['column_name' => 'column_type'], -where the column type is a string representing the expected data type of the column.

    $this->assertDatabaseTable('users', ['id' => 'bigint']);

    getGateMock

    Available in v12.1.0 and above.

    This assertion helps you to test whether the Gate::allows method is invoked with the correct arguments.

    Let's +where the column type is a string representing the expected data type of the column.

    $this->assertDatabaseTable('users', ['id' => 'bigint']);

    getGateMock

    Available since Core v8.5.0

    This assertion helps you to test whether the Gate::allows method is invoked with the correct arguments.

    Let's consider a scenario where a request class utilizes the authorize method to determine whether a user has the necessary permissions to access a particular resource. @@ -100,18 +102,23 @@ The test ensures that the Gate::allows method is invoked with the correct parameters, checking if users have the required permissions to perform updates. If the authorization logic is correctly implemented, this test should pass, -ensuring that only users with the necessary permissions can perform updates.


    inIds

    The inIds method allows you to check if the given hashed ID exists within the provided model collection.

    $hashedId = 'hashed_123';
    $collection = Model::all();

    $isInCollection = $this->inIds($hashedId, $collection);

    By leveraging the inIds method, you can streamline your testing process when working with hashed identifiers, -ensuring that the expected hashed IDs are present within your model collections.

    Deprecation Notice

    This method will be removed in the next major release and will not be available in the test file. -Instead, it will be transformed into a helper function that you can utilize anywhere in your application.

    Faker

    An instance of Faker is automatically provided in every test class, allowing you to generate fake data easily. +ensuring that only users with the necessary permissions can perform updates.


    assertCriteriaPushedToRepository

    Available since Core v8.9.0

    Asserts that a criteria is pushed to a repository.

    In the following example, we want to test +if the UserIsAdminCriteria is pushed to the UserRepository when the ListUsersTask is called with the admin method.

    public function testCanListAdminUsers(): void
    {
    $this->assertCriteriaPushedToRepository(
    UserRepository::class,
    UserIsAdminCriteria::class,
    ['admin' => true],
    );
    $task = app(ListUsersTask::class);

    $task->admin();
    }

    assertNoCriteriaPushedToRepository

    Available since Core v8.9.0

    Asserts that no criteria is pushed to a repository.

    In the following example, we want to test +if no criteria is pushed to the UserRepository when the ListUsersTask's admin method is called with a null value.

    public function testCanListAllUsers(): void
    {
    $this->assertNoCriteriaPushedToRepository(UserRepository::class);
    $task = app(ListUsersTask::class);

    $task->admin(null);
    }

    allowAddRequestCriteriaInvocation

    Available since Core v8.9.0

    Allow addRequestCriteria method invocation on the repository mock. +This is particularly useful when you want to test a repository that uses the +RequestCriteria.

    public function testCanListAdminUsers(): void
    {
    $repositoryMock = $this->assertCriteriaPushedToRepository(
    UserRepository::class,
    UserIsAdminCriteria::class,
    ['admin' => true],
    );
    $this->allowAddRequestCriteriaInvocation($repositoryMock);
    $task = app(ListUsersTask::class);

    $task->admin();
    }

    Faker

    An instance of Faker is automatically provided in every test class, allowing you to generate fake data easily. You can access it using $this->faker.

    Deprecation Notice

    This feature is deprecated and will be removed in the next major release. -You should use the Laravel fake helper function instead.

    Create Live Testing Data

    To test your application using live testing data, +You should use the Laravel fake helper function instead.

    Test Helper Methods

    Apiato provides a variety of helper methods that you may utilize when testing your application.

    createSpyWithRepository
    +inIds


    createSpyWithRepository

    Available since Core v8.9.0

    This method is useful when you want to test if a repository method is called within an Action, SubAction or a Task.

    In the following example, +we want to test if the run method of the CreateUserTask is called within the CreateUserAction.

    public function testCanCreateUser(): void
    {
    $data = [
    'email' => 'gandalf@the.grey',
    'password' => 'you-shall-not-pass',
    ];
    $taskSpy = $this->createSpyWithRepository(CreateUserTask::class, UserRepository::class);
    $action = app(CreateUserAction::class);

    $action->run($data);

    $taskSpy->shouldHaveReceived('run')->once()->with($data);
    }

    inIds

    The inIds method allows you to check if the given hashed ID exists within the provided model collection.

    $hashedId = 'hashed_123';
    $collection = Model::all();

    $isInCollection = $this->inIds($hashedId, $collection);

    By leveraging the inIds method, you can streamline your testing process when working with hashed identifiers, +ensuring that the expected hashed IDs are present within your model collections.

    Deprecation Notice

    This method will be removed in the next major release and will not be available in test classes.

    Create Live Testing Data

    To test your application using live testing data, such as creating items in an inventory, you can utilize the feature designed specifically for this purpose. It allows for the automatic generation of testing data, which can be helpful during staging or when real people are testing your application.

    To create your live testing data, navigate to the app/Ship/Seeder/SeedTestingData.php seeder class. Within this class, you can define the logic and data generation process for your testing data.

    Once you have defined your testing data, you can run the following command in your terminal:

    php artisan apiato:seed-test

    This command triggers the seeding process specifically for testing data, -populating your application with the generated data.

    - +populating your application with the generated data.

    + \ No newline at end of file diff --git a/docs/components/optional-components/values/index.html b/docs/components/optional-components/values/index.html index f8dfcc31e..f217eb843 100644 --- a/docs/components/optional-components/values/index.html +++ b/docs/components/optional-components/values/index.html @@ -4,7 +4,7 @@ Values | Apiato - + @@ -15,7 +15,7 @@ Additionally, they do not possess functionality or modify any state; their sole purpose is to hold data.

    Value Objects are particularly well-suited for use with Laravel attributes casting, which allows us to cast a Value Object to a specific type, enabling seamless integration with Eloquent models and database operations.

    To generate new values you may use the apiato:generate:value interactive command:

    php artisan apiato:generate:value

    Rules

    • All container-specific Values MUST be placed in the app/Containers/{section}/{container}/Values directory.
    • All general Values MUST be placed in the app/Ship/Values directory.
    • All Values MUST extend the App\Ship\Parents\Values\Value class.
      • The parent extension SHOULD be aliased as ParentValue.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Values
    ├── Output.php
    ├── Region.php
    └── ...

    Code Example

    class Location extends Value
    {
    public function __construct(
    public float $latitude,
    public float $longitude,
    ) {
    }
    }
    - + \ No newline at end of file diff --git a/docs/consulting/index.html b/docs/consulting/index.html index ff77b8974..50eb1602c 100644 --- a/docs/consulting/index.html +++ b/docs/consulting/index.html @@ -4,13 +4,13 @@ Consulting | Apiato - +
    - + \ No newline at end of file diff --git a/docs/framework-features/api-versioning/index.html b/docs/framework-features/api-versioning/index.html index c89bb0b78..ed2463182 100644 --- a/docs/framework-features/api-versioning/index.html +++ b/docs/framework-features/api-versioning/index.html @@ -4,13 +4,13 @@ API Versioning | Apiato - +
    - + \ No newline at end of file diff --git a/docs/framework-features/code-generator/index.html b/docs/framework-features/code-generator/index.html index 046ca7c23..e82fa98b7 100644 --- a/docs/framework-features/code-generator/index.html +++ b/docs/framework-features/code-generator/index.html @@ -4,7 +4,7 @@ Code Generator | Apiato - + @@ -22,7 +22,7 @@ However, it is crucial to adhere to one essential rule:

    • The name of the file and the folder structure in app/Ship/Generators/CustomStubs MUST exactly match those in vendor/apiato/core/Generator/Stubs.

    To illustrate the process, let's assume you want to customize the creation of an action. Follow these steps:

    1. Locate the action.stub file in vendor/apiato/core/Generator/Stubs/actions.
    2. Copy the action.stub file and paste it into the app/Ship/Generators/CustomStubs/actions directory.
    3. Make the desired changes to the copied action.stub file according to your requirements.

    By completing these steps, whenever you run the php artisan apiato:generate:action command, your customized stub file will be employed instead of the default one, applying your modifications to the generated action files.

    - + \ No newline at end of file diff --git a/docs/framework-features/etag/index.html b/docs/framework-features/etag/index.html index 0b008eec4..0dc6c5526 100644 --- a/docs/framework-features/etag/index.html +++ b/docs/framework-features/etag/index.html @@ -4,7 +4,7 @@ Etag | Apiato - + @@ -12,7 +12,7 @@
    Version: 12.x

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. It is one of several mechanisms that HTTP provides for Web cache validation, which allows a client to make conditional requests.

    You can read more about this feature in the Requests documentation.

    - + \ No newline at end of file diff --git a/docs/framework-features/index.html b/docs/framework-features/index.html index 16b285b31..4c36b100a 100644 --- a/docs/framework-features/index.html +++ b/docs/framework-features/index.html @@ -4,13 +4,13 @@ Framework Features | Apiato - + - + \ No newline at end of file diff --git a/docs/framework-features/profiler/index.html b/docs/framework-features/profiler/index.html index 540c79250..63432e02b 100644 --- a/docs/framework-features/profiler/index.html +++ b/docs/framework-features/profiler/index.html @@ -4,7 +4,7 @@ Profiler | Apiato - + @@ -17,7 +17,7 @@ Apiato employs the Apiato\Core\Middlewares\Http\ProfilerMiddleware to include the profiling data in the response.

    The profiler feature is initially disabled by default. To enable it, you should edit the .env file and set DEBUGBAR_ENABLED=true.

    To customize and manage the profiler response, you'll need to make adjustments in the configuration file located at app/Ship/Configs/debugbar.php.

    The following is an example of the profiler response:

    {
    "_profiler": {
    "__meta": {
    "id": "X167f293230e3457f1bbd95d9c82aba4a",
    "datetime": "2017-09-22 18:45:27",
    "utime": 1506105927.799299,
    "method": "GET",
    "uri": "/",
    "ip": "172.20.0.1"
    },
    "messages": {
    "count": 0,
    "messages": []
    },
    "time": {
    "start": 1506105922.742068,
    "end": 1506105927.799333,
    "duration": 5.057265043258667,
    "duration_str": "5.06s",
    "measures": [
    {
    "label": "Booting",
    "start": 1506105922.742068,
    "relative_start": 0,
    "end": 1506105923.524004,
    "relative_end": 1506105923.524004,
    "duration": 0.7819359302520752,
    "duration_str": "781.94ms",
    "params": [],
    "collector": null
    },
    {
    "label": "Application",
    "start": 1506105923.535343,
    "relative_start": 0.7932748794555664,
    "end": 1506105927.799336,
    "relative_end": 0.00000286102294921875,
    "duration": 4.26399302482605,
    "duration_str": "4.26s",
    "params": [],
    "collector": null
    }
    ]
    },
    "memory": {
    "peak_usage": 13234248,
    "peak_usage_str": "12.62MB"
    },
    "exceptions": {
    "count": 0,
    "exceptions": []
    },
    "route": {
    "uri": "GET /",
    "middleware": "api, throttle:30,1",
    "domain": "http://api.apiato.test",
    "as": "apis_root_page",
    "controller": "App\\Containers\\Welcome\\UI\\API\\Controllers\\Controller@apiRoot",
    "namespace": "App\\Containers\\Welcome\\UI\\API\\Controllers",
    "prefix": "/",
    "where": [],
    "file": "app/Containers/Welcome/UI/API/Controllers/Controller.php:20-25"
    },
    "queries": {
    "nb_statements": 0,
    "nb_failed_statements": 0,
    "accumulated_duration": 0,
    "accumulated_duration_str": "0μs",
    "statements": []
    },
    "logs": {
    "count": 3,
    "messages": [
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694807
    },
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694811
    },
    {
    "message": "[2017-09-18 17:38:15] testing.INFO: New User registration. ID = 970ylqvaogmxnbdr | Email = apiato@mail.test. Thank you for signing up.\n</div>\n</body>\n</html>\n \n",
    "message_html": null,
    "is_string": false,
    "label": "info",
    "time": 1506105927.694812
    }
    ]
    },
    "auth": {
    "guards": {
    "web": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]",
    "api": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]"
    },
    "names": ""
    },
    "gate": {
    "count": 0,
    "messages": []
    }
    }
    }
    - + \ No newline at end of file diff --git a/docs/framework-features/rate-limiting/index.html b/docs/framework-features/rate-limiting/index.html index 86994ba28..4ead90530 100644 --- a/docs/framework-features/rate-limiting/index.html +++ b/docs/framework-features/rate-limiting/index.html @@ -4,7 +4,7 @@ Rate Limiting | Apiato - + @@ -17,7 +17,7 @@ You can disable it on specific endpoints or globally.

    On a specific endpoint

    Rate limiting can be disabled by removing the api middleware from an endpoint using withoutMiddleware('throttle:api') method. Read More.

    Globally

    To disable rate limiting completely, set GLOBAL_API_RATE_LIMIT_ENABLED to false in the .env file.

    - + \ No newline at end of file diff --git a/docs/framework-features/rbac/index.html b/docs/framework-features/rbac/index.html index 2a52c831f..33804af9e 100644 --- a/docs/framework-features/rbac/index.html +++ b/docs/framework-features/rbac/index.html @@ -4,7 +4,7 @@ Role Based Access Control | Apiato - + @@ -12,7 +12,7 @@
    - + \ No newline at end of file diff --git a/docs/getting-started/best-practices/index.html b/docs/getting-started/best-practices/index.html index 2c6d0b557..71654aa4b 100644 --- a/docs/getting-started/best-practices/index.html +++ b/docs/getting-started/best-practices/index.html @@ -4,7 +4,7 @@ Best Practices | Apiato - + @@ -45,7 +45,7 @@ This gives maintainers of the API enough information to understand the problem that’s occurred. We don’t want errors to bring down our system, so we can leave them unhandled, which means that the API consumer has to handle them.

    Common error HTTP status codes include:

    • 200 OK Server successfully returned the requested data.
    • 201 CREATED Server successfully created or modified the requested resource.
    • 204 NO CONTENT Server successfully deleted the requested resource.
    • 400 INVALID REQUEST The request was invalid or cannot be served. The exact error should be explained in the error payload. E.g. „The JSON is not valid“.
    • 401 UNAUTHORIZED The request requires an user authentication.
    • 403 FORBIDDEN The server understood the request, but is refusing it or the access is not allowed.
    • 404 NOT FOUND There is no resource behind the URI.
    • 422 Unprocessable Entity Should be used if the server cannot process the enitity, e.g. if an image cannot be formatted or mandatory fields are missing in the payload.
    • 500 INTERNAL SERVER ERROR Internal Server Error.
    • 502 BAD GATEWAY Server received an invalid response from the upstream server while trying to fulfill the request.
    • 503 SERVICE UNAVAILABLE Service unavailable.

    Naming Conventions For Routes & Actions

    • ListResources: to fetch all resources.
    • FindResourceByID: to search for a single resource by its unique identifier.
    • CreateResource: to create a new resource.
    • UpdateResource: to update/edit existing resource.
    • DeleteResource: to delete a resource.
    - + \ No newline at end of file diff --git a/docs/getting-started/customized-laravel-components/index.html b/docs/getting-started/customized-laravel-components/index.html index 3769feb38..090286086 100644 --- a/docs/getting-started/customized-laravel-components/index.html +++ b/docs/getting-started/customized-laravel-components/index.html @@ -4,14 +4,14 @@ Customized Laravel Components | Apiato - +
    Version: 12.x

    Customized Laravel Components

    Apiato provides a refined organization for Laravel default class locations. Here, you can find the default Laravel components and their corresponding locations within Apiato.

    Kernels

    • Http Kernel is moved from app/Http to app/Ship/Kernels and renamed to HttpKernel.

    • Console Kernel is moved from app/Console to app/Ship/Kernels and renamed to ConsoleKernel.

    Middlewares

    • Middlewares are moved from app/Http/Middleware to app/Ship/Middlewares.

    Handler

    • Exception Handler is moved from app/Exceptions to app/Ship/Exceptions/Handlers and renamed to ExceptionsHandler.

    Providers

    • For information about the new locations of Providers, please refer to this link.

    Routes

    Web and API

    Apiato introduces a new approach to route organization and does not use the default routes/web.php and routes/api.php files. Therefore, you won't find these files in Apiato. To learn more, please visit this link.

    Channels

    • The channels.php file has been relocated from routes to app/Ship/Broadcasts.

    Console

    • The console.php file has been moved from routes to app/Ship/Commands and renamed to closures.php.
    - + \ No newline at end of file diff --git a/docs/getting-started/installation/index.html b/docs/getting-started/installation/index.html index edebdbecd..1ef88377e 100644 --- a/docs/getting-started/installation/index.html +++ b/docs/getting-started/installation/index.html @@ -4,7 +4,7 @@ Installation | Apiato - + @@ -42,7 +42,7 @@ assuming you are using the default Subdomain and API Version Prefix configuration, you should be able to access the following URLs and see the following results:

    Web (Browser)

    API (HTTP Client)

    Next Steps

    Now that you have created your Apiato project, you may be wondering what to learn next. If you're looking for a place to start, you should check out the following resources:

    - + \ No newline at end of file diff --git a/docs/next/architecture-concepts/components/index.html b/docs/next/architecture-concepts/components/index.html index 95f42be91..a11321585 100644 --- a/docs/next/architecture-concepts/components/index.html +++ b/docs/next/architecture-concepts/components/index.html @@ -4,7 +4,7 @@ Components | Apiato - + @@ -19,7 +19,7 @@ simplifying future maintenance and facilitating modifications when needed.

    Components in Porto are categorized into two types: Main Components and Optional Components.

    Main Components

    Main Components are essential for the Container's functionality and must be used to achieve its core purpose.

    Optional Components

    Optional Components offer additional functionality that can be incorporated into the Container. Their usage is discretionary, depending on specific requirements.

    tip

    To learn more about how all this fits together, read the Request Lifecycle page.

    - + \ No newline at end of file diff --git a/docs/next/architecture-concepts/container/index.html b/docs/next/architecture-concepts/container/index.html index 5121f7ab7..7521269c2 100644 --- a/docs/next/architecture-concepts/container/index.html +++ b/docs/next/architecture-concepts/container/index.html @@ -4,7 +4,7 @@ Container | Apiato - + @@ -23,7 +23,7 @@ you may use the apiato:generate:container interactive command:

    php artisan apiato:generate:container

    Composer Dependencies

    To manage Composer dependencies, follow these guidelines:

    • All the Composer dependencies for a specific Container should be defined within that Container's composer.json file.
    • Dependencies related to the Ship layer should be placed in the root of the Ship layer, in a composer.json file.
    • Framework core dependencies should be defined in the project's root-level composer.json file.

    In practice, you can choose to place Composer dependencies in any of these composer.json files, and they will perform the same function. The choice of location depends on what is most relevant and convenient for your project.

    Readme

    Each Container has the option to include a readme.md file at its root, which serves to explain the Container's purpose and how to use it.

    To generate new readme files, you may use the apiato:generate:readme interactive command:

    php artisan apiato:generate:readme
    - + \ No newline at end of file diff --git a/docs/next/architecture-concepts/index.html b/docs/next/architecture-concepts/index.html index 8cd1e89cf..8d345a64b 100644 --- a/docs/next/architecture-concepts/index.html +++ b/docs/next/architecture-concepts/index.html @@ -4,7 +4,7 @@ Architecture Concepts | Apiato - + @@ -13,7 +13,7 @@ to structure the application code.

    Investing 30 minutes in reading the Porto Documentation before getting started is highly recommended and can prove to be a valuable use of your time. The document serves as a comprehensive guide and resource for understanding the Apiato project.

    - + \ No newline at end of file diff --git a/docs/next/architecture-concepts/porto/index.html b/docs/next/architecture-concepts/porto/index.html index 32e1315ad..6291e439c 100644 --- a/docs/next/architecture-concepts/porto/index.html +++ b/docs/next/architecture-concepts/porto/index.html @@ -4,7 +4,7 @@ Porto | Apiato - + @@ -18,7 +18,7 @@ It can be a specific feature or a wrapper around a RESTful API resource.

    note

    A Container is allowed to depend on other Containers in the same Section.

    Ship

    The Ship layer contains the infrastructure code, which consists of shared code utilized by all Containers.

    Typical Project Structure

    app
    ├── Containers
    │ ├── Section
    │ │ └── Container
    │ │ ├── Actions
    │ │ ├── Configs
    │ │ ├── Data
    │ │ │ ├── Factories
    │ │ │ ├── Migrations
    │ │ │ ├── Repositories
    │ │ │ └── Seeders
    │ │ ├── Mails
    │ │ │ └── Templates
    │ │ ├── Middlewares
    │ │ ├── Models
    │ │ ├── Notifications
    │ │ ├── Providers
    │ │ ├── Tasks
    │ │ ├── Tests
    │ │ ├── Traits
    │ │ └── UI
    │ │ ├── API
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Routes
    │ │ │ └── Transformers
    │ │ ├── WEB
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Routes
    │ │ │ └── Views
    │ │ └── CLI
    │ │ └── Commands
    │ └── Vendor `// All installed and reusable Containers`
    │ ├── ContainerA
    │ └── ContainerB
    └── Ship `// All shared code between all Containers`
    ├── Broadcasts
    ├── Commands
    ├── Configs
    ├── Contracts
    ├── Criterias
    ├── Events
    ├── Exceptions
    ├── Generators
    ├── Helpers
    ├── Kernels
    ├── Listeners
    ├── Mails
    ├── Middlewares
    ├── Migrations
    ├── Notifications
    ├── Parents
    ├── Providers
    ├── Seeders
    └── Tests

    Default Sections

    Apiato ships with two default Sections:

    • AppSection: contains all the default Containers.
    • Vendor: contains all the installed and reusable Containers.
    tip

    The Vendor section is a special Section within the Containers layer that holds installed and reusable Containers. It serves a similar purpose as the vendor folder located at the root. Any Section is permitted to depend on the Vendor Section, allowing for the utilization of its Containers.

    Read more about the Container Installer to learn how to install Vendor Containers.

    - + \ No newline at end of file diff --git a/docs/next/architecture-concepts/request-lifecycle/index.html b/docs/next/architecture-concepts/request-lifecycle/index.html index c43ccdc85..93ab44124 100644 --- a/docs/next/architecture-concepts/request-lifecycle/index.html +++ b/docs/next/architecture-concepts/request-lifecycle/index.html @@ -4,7 +4,7 @@ Request Lifecycle | Apiato - + @@ -16,7 +16,7 @@ The Tasks can be used to execute reusable subsets of the business logic, with each Task responsible for a single portion of the main Action. The View or Transformer is used to build the response that is sent back to the User.

    Request Lifecycle Diagram

    Legend:

    • Solid Line: Mandatory dependency (always used)
    • Doted Line: Optional dependency (not always used)
    • Red Solid Border: Response generation
    • Green Dashed Border: Optional component (not always used)
    - + \ No newline at end of file diff --git a/docs/next/components/index.html b/docs/next/components/index.html index 817fd13f1..59754b7ed 100644 --- a/docs/next/components/index.html +++ b/docs/next/components/index.html @@ -4,7 +4,7 @@ Components | Apiato - + @@ -13,7 +13,7 @@ "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

    info

    To learn more about Apiato components, check the Components section.

    - + \ No newline at end of file diff --git a/docs/next/components/main-components/actions/index.html b/docs/next/components/main-components/actions/index.html index b608469a8..df04f6389 100644 --- a/docs/next/components/main-components/actions/index.html +++ b/docs/next/components/main-components/actions/index.html @@ -4,7 +4,7 @@ Actions | Apiato - + @@ -20,7 +20,7 @@ However, it's important to note that not all operations may be automatically rolled back. For example, file system operations, such as uploading an image, are typically not covered by the database transaction and would need to be handled manually.

    - + \ No newline at end of file diff --git a/docs/next/components/main-components/controllers/index.html b/docs/next/components/main-components/controllers/index.html index 669c358ae..cd059aa9c 100644 --- a/docs/next/components/main-components/controllers/index.html +++ b/docs/next/components/main-components/controllers/index.html @@ -4,7 +4,7 @@ Controllers | Apiato - + @@ -16,7 +16,7 @@ and it MUST be used in conjunction with the transform method. This is different from the meta parameter in the transform method. This metadata will be returned directly under the meta key.

    You can use this method in conjunction with the meta parameter in the transform method.

    $metaData = ['foo' => 999, 'bar'];

    $this->withMeta($metaData)->transform($sample, SampleTransformer::class, meta: ['foo' => 'bar', 'baz' => 1]);

    // Response
    {
    "data": {},
    "meta": {
    "foo": 999,
    "0": "bar",
    "include": [...],
    "custom": {
    "foo": "bar",
    "baz": 1
    },
    "pagination": {}
    }
    }

    json

    This method allows you to pass an array of data that will be represented as JSON.

    $this->json($data)

    created

    This method allows you to return a response with a 201 status code.

    $this->created($data)

    deleted

    This method allows you to return a response with a 202 status code.

    $this->deleted($deletedModel)

    // Response
    {
    "message": "Model (1) Deleted Successfully."
    }

    accepted

    This method allows you to return a response with a 202 status code.

    $this->accepted($data)

    noContent

    This method allows you to return a response with a 204 status code.

    $this->noContent()
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/exceptions/index.html b/docs/next/components/main-components/exceptions/index.html index 564b0d8f0..f6ea6ac2c 100644 --- a/docs/next/components/main-components/exceptions/index.html +++ b/docs/next/components/main-components/exceptions/index.html @@ -4,7 +4,7 @@ Exceptions | Apiato - + @@ -13,7 +13,7 @@ Translation strings are automatically translated if the translations are found. To handle localization, you can use the Localization Container.

    // Example 1
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken']);
    // Example 2
    throw (new AccountFailedException())->withErrors(['email' => 'appSection@user::exceptions.email-taken', 'Another not translated message']);

    Response:

    {
    "message": "The exception error message.",
    "errors": {
    "email": [
    "The email has already been taken.",
    "Another not translated message"
    ]
    }
    }

    debug

    The debug method is used for logging error messages during debugging and development. The debug method accepts string or \Exception instance

    throw (new AccountFailedException())->debug($e);
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/index.html b/docs/next/components/main-components/index.html index f14a6ae12..8b3939be7 100644 --- a/docs/next/components/main-components/index.html +++ b/docs/next/components/main-components/index.html @@ -4,13 +4,13 @@ Main Components | Apiato - +
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/models/index.html b/docs/next/components/main-components/models/index.html index 3b581dc70..8ab6b09d2 100644 --- a/docs/next/components/main-components/models/index.html +++ b/docs/next/components/main-components/models/index.html @@ -4,7 +4,7 @@ Models | Apiato - + @@ -16,7 +16,7 @@ it is essential to incorporate the ModelTrait trait into your model. By doing so, your model will benefit from various functionalities provided by the trait, such as hash ids and other features necessary for proper integration with the framework.

    use Apiato\Core\Traits\ModelTrait;

    class Demo
    {
    use ModelTrait;
    ...
    }
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/requests/index.html b/docs/next/components/main-components/requests/index.html index de5e74910..0a91f3493 100644 --- a/docs/next/components/main-components/requests/index.html +++ b/docs/next/components/main-components/requests/index.html @@ -4,7 +4,7 @@ Requests | Apiato - + @@ -73,7 +73,7 @@ which employs the Shallow technique. This middleware can be particularly valuable in reducing bandwidth usage for clients, especially on mobile devices.

    Please note that this feature is disabled by default. To enable it, follow these steps:

    1. Navigate to the app/Ship/Configs/apiato.php configuration file.
    2. Inside the configuration file, locate the use-etag configuration parameter.
    3. Set the use-etag parameter to true.

    Keep in mind that for this feature to function correctly, the client must include the If-None-Match HTTP header, which corresponds to the ETag value, in their request.

    - + \ No newline at end of file diff --git a/docs/next/components/main-components/routes/index.html b/docs/next/components/main-components/routes/index.html index 1a982274b..7a5268cd9 100644 --- a/docs/next/components/main-components/routes/index.html +++ b/docs/next/components/main-components/routes/index.html @@ -4,7 +4,7 @@ Routes | Apiato - + @@ -18,7 +18,7 @@ Maintaining this distinction enables the generation of separate documentations for each type, ensuring that your internal API remains private and secure. This feature can be configured through the Documentation Generator package.

    Public Routes:

    • Accessible to third parties.
    • May or may not require authentication.

    Private Routes:

    • Accessible only to your own apps.
    • May or may not require authentication.
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/subactions/index.html b/docs/next/components/main-components/subactions/index.html index c341958e3..0dc212b96 100644 --- a/docs/next/components/main-components/subactions/index.html +++ b/docs/next/components/main-components/subactions/index.html @@ -4,7 +4,7 @@ Sub Actions | Apiato - + @@ -13,7 +13,7 @@ They enable Actions to share a sequence of Tasks, thus promoting reusability. Similar to how Tasks enable Actions to share specific functionalities, SubActions serve to share a predefined set of Tasks.

    All the features and capabilities available for regular Actions are also applicable to SubActions.

    To generate new SubActions you may use the apiato:generate:subaction interactive command:

    php artisan apiato:generate:subaction

    Definition & Principles

    Read Porto SAP Documentation (#Sub-Actions).

    Rules

    • All SubActions:
      • MUST be placed in the app/Containers/{Section}/{Container}/Actions directory.
      • MUST extend the App\Ship\Parents\Actions\SubAction class.
        • The parent extension SHOULD be aliased as ParentSubAction.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Actions
    ├── ValidateAddressSubAction.php
    ├── BuildOrderSubAction.php
    └── ...

    Code Example

    use ...
    use App\Ship\Parents\Actions\SubAction as ParentSubAction;

    class DemoSubAction extends ParentSubAction
    {
    public function __construct(
    private readonly DemoTask $demoTask
    ) {
    }

    public function run(array $data)
    {
    return $this->demoTask->run($data);
    }
    }
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/tasks/index.html b/docs/next/components/main-components/tasks/index.html index 4357a2b98..57fcf1788 100644 --- a/docs/next/components/main-components/tasks/index.html +++ b/docs/next/components/main-components/tasks/index.html @@ -4,7 +4,7 @@ Tasks | Apiato - + @@ -15,7 +15,7 @@ in encapsulating functionalities that are utilized by multiple Actions spanning various Containers within your application.

    To generate new tasks you may use the apiato:generate:task interactive command:

    php artisan apiato:generate:task

    Additionally, to retrieve a list of the existing tasks in your Apiato application, use the apiato:list:tasks command.

    php artisan apiato:list:tasks

    Definition & Principles

    Read Porto SAP Documentation (#Tasks).

    Rules

    • All Tasks:
      • MUST be placed in the app/Containers/{Section}/{Container}/Tasks directory.
      • MUST extend the App\Ship\Parents\Tasks\Task class.
        • The parent extension SHOULD be aliased as ParentTask.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Tasks
    ├── CreateResourceTask.php
    ├── DeleteResourceTask.php
    └── ...

    Code Example

    use ...
    use App\Ship\Parents\Tasks\Task as ParentTask;

    class DemoTask extends ParentTask
    {
    public function run(int $a, int $b): int
    {
    return $a + $b;
    }
    }
    - + \ No newline at end of file diff --git a/docs/next/components/main-components/transformers/index.html b/docs/next/components/main-components/transformers/index.html index b9b9b1b8e..62c42ee9a 100644 --- a/docs/next/components/main-components/transformers/index.html +++ b/docs/next/components/main-components/transformers/index.html @@ -4,7 +4,7 @@ Transformers | Apiato - + @@ -54,7 +54,7 @@ This serializer is not to everyone’s tastes, because it adds a data namespace to the output. A very basic response of the DataArraySerializer will look like this:

    {
    "data": {
    "object": "User",
    "id": "XbPW7awNkzl83LD6",
    "name": "Mohammad Alavi"
    }
    }

    The DataArraySerializer is handy because it allows space for meta data (like pagination, or totals) in both Items and Collections.

    {
    "data": [ ... ],
    "meta": {
    "include": [
    "xxx",
    "yyy"
    ],
    "custom": [],
    "pagination": {
    "total": 999,
    "count": 999,
    "per_page": 999,
    "current_page": 999,
    "total_pages": 999,
    "links": {
    "next": "http://api.apiato.test/v1/accounts?page=999"
    }
    }
    }
    }
    Further Reading

    For more detailed information, please refer to Fractal and Laravel Fractal Wrapper documentations.

    - + \ No newline at end of file diff --git a/docs/next/components/main-components/views/index.html b/docs/next/components/main-components/views/index.html index 79a270755..785d4ff85 100644 --- a/docs/next/components/main-components/views/index.html +++ b/docs/next/components/main-components/views/index.html @@ -4,7 +4,7 @@ Views | Apiato - + @@ -17,7 +17,7 @@ such as view('welcome-page'), will result in the view not being found.

    An exception to this namespace convention is for view files located in the app/Ship/Views and app/Ship/Mails/Templates directories. These views will be namespaced using the word ship instead of the Section and Container names.

    For example, you would access such a view like this: view(ship::welcome-page).

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/commands/index.html b/docs/next/components/optional-components/commands/index.html index bcd8d2b3a..86f83a1b3 100644 --- a/docs/next/components/optional-components/commands/index.html +++ b/docs/next/components/optional-components/commands/index.html @@ -4,7 +4,7 @@ Commands | Apiato - + @@ -12,7 +12,7 @@
    Version: Next 🚧

    Commands

    Apiato commands are just Laravel Commands, and they function in the exact same way as Laravel commands. However, they come with additional rules and conventions specific to Apiato.

    Rules

    • All container-specific Commands MUST be placed in the app/Containers/{Section}/{Container}/UI/CLI/Commands directory.
    • All general Commands MUST be placed in the app/Ship/Commands directory.
    • All Commands:
      • MUST extend the App\Ship\Parents\Commands\ConsoleCommand class.
        • The parent extension SHOULD be aliased as ConsoleCommand.
      • SHOULD call an Action to perform its job, and SHOULD NOT contain any business logic.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── UI
    │ └── CLI
    │ └── Commands
    │ ├── FirstCommand.php
    │ ├── SecondCommand.php
    │ └── ...
    └── Ship
    └── Commands
    ├── FirstCommand.php
    ├── SecondCommand.php
    └── ...

    Code Example

    Commands are defined exactly as you would define them in Laravel.

    Closure Commands

    You can define your console closure commands in app/Ship/Commands/closures.php.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/configs/index.html b/docs/next/components/optional-components/configs/index.html index 5333169f2..37d83debd 100644 --- a/docs/next/components/optional-components/configs/index.html +++ b/docs/next/components/optional-components/configs/index.html @@ -4,7 +4,7 @@ Configs | Apiato - + @@ -17,7 +17,7 @@ camelCase representation of the container's Section name, succeeded by -, and then the camelCase representation of the Container name.

    For instance, if you have a container named "MyContainer" within the "MySection" section, the configuration file would be named mySection-myContainer.php.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/events/index.html b/docs/next/components/optional-components/events/index.html index 8089358bb..9adc9db44 100644 --- a/docs/next/components/optional-components/events/index.html +++ b/docs/next/components/optional-components/events/index.html @@ -4,7 +4,7 @@ Events | Apiato - + @@ -17,7 +17,7 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the EventServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    EventServiceProvider::class,
    ];
    }

    In The Ship

    Registering events and listeners in the Ship can be done by adding them to the listen array in the App\Ship\Providers\EventServiceProvider class.

    Events & Listeners Registration Flow

    If you are manually registering events and listeners and wish to understand the registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ ├── Events
    │ │ ├── DemoEvent.php ────►─┐
    │ │ └── ...
    │ ├── Listeners │
    │ │ ├── DemoListener.php ─►─┤
    │ │ └── ...
    │ └── Providers ▼
    │ ├── EventServiceProvider.php ─────────►─────────┐
    │ ├── MainServiceProvider.php ◄───registered─in─◄─┘
    │ └── ...
    └── Ship
    ├── Events
    │ ├── ShipDemoEvent.php ──►─┐
    │ └── ...
    ├── Listeners │
    │ ├── ShipDemoListener.php ►┤
    │ └── ...
    └── Providers ▼
    ├── EventServiceProvider.php ─────────►─────────┐
    ├── ShipProvider.php ◄───registered─in─◄─┘
    └── ...

    The following diagram illustrates the registration flow of events and listeners in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/factories/index.html b/docs/next/components/optional-components/factories/index.html index 5a734bee4..b476da7b5 100644 --- a/docs/next/components/optional-components/factories/index.html +++ b/docs/next/components/optional-components/factories/index.html @@ -4,7 +4,7 @@ Factories | Apiato - + @@ -22,7 +22,7 @@ if your model does not extend the App\Ship\Parents\Models\Model or the App\Ship\Parents\Models\UserModel class, it is essential to include the ModelTrait trait in your model. By doing so, Apiato will be able to locate the appropriate factory and use it for the model when needed.

    use Apiato\Core\Traits\ModelTrait;

    class Demo
    {
    use ModelTrait;
    ...
    }
    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/helpers/index.html b/docs/next/components/optional-components/helpers/index.html index b23706cd1..0e67cd992 100644 --- a/docs/next/components/optional-components/helpers/index.html +++ b/docs/next/components/optional-components/helpers/index.html @@ -4,13 +4,13 @@ Helpers | Apiato - +
    Version: Next 🚧

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Deprecation Notice

    Please be aware that Container Helpers are deprecated and will be removed in the next major release.

    Rules

    • You MAY create as many helper files as you need per container.
    • All container-specific helpers MUST be placed in the app/Containers/{Section}/{Container}/Helpers directory.
    • All general helpers MUST be placed in the app/Ship/Helpers directory.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Helpers
    │ ├── helpers.php
    │ ├── mix.php
    │ └── ...
    └── Ship
    └── Helpers
    ├── another-helper.php
    ├── and-another.php
    └── ...

    Code Example

    if (!function_exists('add')) {
    function add(int $firstNumber, int $secondNumber): int
    {
    return $firstNumber + $secondNumber;
    }
    }
    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/index.html b/docs/next/components/optional-components/index.html index 2a6d6b8d3..710c12126 100644 --- a/docs/next/components/optional-components/index.html +++ b/docs/next/components/optional-components/index.html @@ -4,7 +4,7 @@ Optional Components | Apiato - + @@ -14,7 +14,7 @@ Their usage is discretionary, depending on specific requirements.

    Most of these components are just Laravel components, and they function in the exact same way as Laravel components. However, they come with additional rules and conventions specific to Apiato.

    info

    To learn more about Apiato components, check the Components section.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/jobs/index.html b/docs/next/components/optional-components/jobs/index.html index 878ecb561..be1a177c7 100644 --- a/docs/next/components/optional-components/jobs/index.html +++ b/docs/next/components/optional-components/jobs/index.html @@ -4,7 +4,7 @@ Jobs | Apiato - + @@ -12,7 +12,7 @@
    Version: Next 🚧

    Jobs

    Apiato jobs are just Laravel Jobs, and they function in the exact same way as Laravel jobs. However, they come with additional rules and conventions specific to Apiato.

    To generate new jobs you may use the apiato:generate:job interactive command:

    php artisan apiato:generate:job

    Rules

    • All container-specific Jobs MUST be placed in the app/Containers/{Section}/{Container}/Jobs directory.
    • All general Jobs MUST be placed in the app/Ship/Jobs directory.
    • All Jobs MUST extend the App\Ship\Parents\Jobs\Job class.
      • The parent extension SHOULD be aliased as ParentJob.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Jobs
    │ ├── FooJob.php
    │ ├── BarJob.php
    │ └── ...
    └── Ship
    └── Jobs
    ├── BazJob.php
    └── ...

    Code Example

    Jobs are defined exactly as you would define them in Laravel.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/mail/index.html b/docs/next/components/optional-components/mail/index.html index e9cd29acb..aa110250d 100644 --- a/docs/next/components/optional-components/mail/index.html +++ b/docs/next/components/optional-components/mail/index.html @@ -4,7 +4,7 @@ Mail | Apiato - + @@ -18,7 +18,7 @@ such as view('welcome'), will result in the template not being found.

    An exception to this namespace convention is for template files located in the app/Ship/Mails/Templates directory. These templates will be namespaced using the word ship instead of the Section and Container names.

    For example, you would access such a template like this: view(ship::welcome).

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/middlewares/index.html b/docs/next/components/optional-components/middlewares/index.html index 1d528a9f6..852a0c94a 100644 --- a/docs/next/components/optional-components/middlewares/index.html +++ b/docs/next/components/optional-components/middlewares/index.html @@ -4,7 +4,7 @@ Middlewares | Apiato - + @@ -19,7 +19,7 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the MiddlewareServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    MiddlewareServiceProvider::class,
    ];
    }

    General Middlewares

    General middlewares must be registered in the App\Ship\Kernels\HttpKernel class.

    Third Party Middlewares

    When dealing with third-party packages that require middleware registration in the App\Ship\Kernels\HttpKernel class, you should follow these guidelines:

    • Specific Container Usage: If the package is used within a particular container, register its middleware in that container App\Containers\{Section}\{Container}\Providers\MiddlewareServiceProvider class.

    • Framework-wide Usage: If the package is generic and used throughout the entire application, you can register its middleware in the App\Ship\Kernels\HttpKernel class.

    Middleware Registration Flow

    If you want to understand the middleware registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ ├── Middlewares
    │ │ ├── DemoMiddleware.php ─►─┐
    │ │ └── ...
    │ └── Providers ▼
    │ ├── MiddlewareServiceProvider.php ─────►─────┐
    │ ├── MainServiceProvider.php ◄─registered─in─◄┘
    │ └── ...
    └── Ship
    ├── Kernels
    │ ├── HttpKernel.php ◄─registered─in─◄┐
    │ └── ...
    └── Middlewares │
    ├── AnotherMiddleware.php ─────►────┘
    └── ...

    The following diagram illustrates the registration flow of middlewares in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/migrations/index.html b/docs/next/components/optional-components/migrations/index.html index e9e17cd88..73cc69c4d 100644 --- a/docs/next/components/optional-components/migrations/index.html +++ b/docs/next/components/optional-components/migrations/index.html @@ -4,7 +4,7 @@ Migrations | Apiato - + @@ -12,7 +12,7 @@
    Version: Next 🚧

    Migrations

    Apiato migrations are just Laravel Migrations, and they function in the exact same way as Laravel migrations. However, they come with additional rules and conventions specific to Apiato.

    To generate new migrations you may use the apiato:generate:migration interactive command:

    php artisan apiato:generate:migration

    Rules

    • All container-specific Migrations MUST be placed in the app/Containers/{Section}/{Container}/Data/Migrations directory.
    • All general Migrations MUST be placed in the app/Ship/Migrations directory.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Data
    │ └── Migrations
    │ ├── 0000_01_01_000001_create_things_table.php
    │ └── ...
    └── Ship
    └── Migrations
    ├── 0000_02_02_000002_create_another_things_table.php
    └── ...

    Code Example

    Migrations are defined exactly as you would define them in Laravel.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/notifications/index.html b/docs/next/components/optional-components/notifications/index.html index a1d8b2f2c..531d9f138 100644 --- a/docs/next/components/optional-components/notifications/index.html +++ b/docs/next/components/optional-components/notifications/index.html @@ -4,7 +4,7 @@ Notifications | Apiato - + @@ -24,7 +24,7 @@ As a result, you don't need to manually generate the migration file. You can directly run the migrations using the php artisan migrate command, and the notifications table will be created for you.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/policies/index.html b/docs/next/components/optional-components/policies/index.html index f69ed3b69..8c87db7d0 100644 --- a/docs/next/components/optional-components/policies/index.html +++ b/docs/next/components/optional-components/policies/index.html @@ -4,7 +4,7 @@ Policies | Apiato - + @@ -18,10 +18,10 @@ you may use the apiato:generate:provider interactive command:

    php artisan apiato:generate:provider

    Remember to also register the AuthServiceProvider in the container's MainServiceProvider:

    use ...
    use App\Ship\Parents\Providers\MainServiceProvider as ParentMainServiceProvider;

    class MainServiceProvider extends ParentMainServiceProvider
    {
    protected array $serviceProviders = [
    // ... Other service providers
    AuthServiceProvider::class,
    ];
    }

    Policy Auto-Discovery

    Apiato offers a policy auto-discovery feature that eliminates the need for manual registration of model policies. This automatic discovery process relies on adhering to standard Apiato naming conventions for policies.

    By following the rules outlined above, you allow Apiato to automatically discover your policies.

    To summarize:

    • Policies must be stored within the app/Containers/{section}/{container}/Policies directory.
    • The policy name should mirror the corresponding model's name while appending a Policy suffix. For instance, a User model corresponds to a UserPolicy policy class.

    Policy Registration Flow

    In case you are going to register your policies manually, and don't want to use the auto-discovery feature, you may want to understand the policy registration process. -Here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    └── Containers
    └── Section
    └── Container
    ├── Policies
    │ ├── DemoPolicy.php ─►─┐
    │ └── ...
    └── Providers ▼
    ├── AuthServiceProvider.php ─────────►───────┐
    ├── MainServiceProvider.php ◄─registered─in─◄┘
    └── ...

    The following diagram illustrates the registration flow of policies in the above folder structure:

    Helper Methods

    Available in v12.2.0 and above.

    All models are equipped with the owns and isOwnedBy methods, +Here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    └── Containers
    └── Section
    └── Container
    ├── Policies
    │ ├── DemoPolicy.php ─►─┐
    │ └── ...
    └── Providers ▼
    ├── AuthServiceProvider.php ─────────►───────┐
    ├── MainServiceProvider.php ◄─registered─in─◄┘
    └── ...

    The following diagram illustrates the registration flow of policies in the above folder structure:

    Helper Methods

    Available since Core v8.7.0

    All models are equipped with the owns and isOwnedBy methods, made available through the Apiato\Core\Traits\CanOwnTrait trait. -These methods offer a convenient way to determine if a model is owned by another model or if a model owns another model.

    These methods support all types of relationships, as demonstrated below:

    // Check if a user owns a post
    $user->owns($post);

    // Check if a post is owned by a user
    $post->isOwnedBy($user);
    - +These methods offer a convenient way to determine if a model is owned by another model or if a model owns another model.

    These methods support all types of relationships, as demonstrated below:

    // Check if a user owns a post
    $user->owns($post);

    // Check if a post is owned by a user
    $post->isOwnedBy($user);
    + \ No newline at end of file diff --git a/docs/next/components/optional-components/repository/criterias/index.html b/docs/next/components/optional-components/repository/criterias/index.html index de39d1884..d894631bc 100644 --- a/docs/next/components/optional-components/repository/criterias/index.html +++ b/docs/next/components/optional-components/repository/criterias/index.html @@ -4,7 +4,7 @@ Criterias | Apiato - + @@ -23,7 +23,7 @@ This approach offers the flexibility to create query conditions once and apply them consistently anywhere in your application, enhancing code reusability and maintainability.

    Rules

    • All container-specific Criterias MUST be placed in the app/Containers/{Section}/{Container}/Data/Criterias directory.
    • All general Criterias MUST be placed in the app/Ship/Criterias directory.
    • All Criterias MUST extend the App\Ship\Parents\Criterias\Criteria class.
      • The parent extension SHOULD be aliased as ParentCriteria.
    • Every Criteria MUST have an apply method.
    • A Criteria MUST not contain any extra code. If it needs data, the data MUST be passed to it.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Data
    │ └── Criterias
    │ ├── ColourRedCriteria.php
    │ ├── RaceCarsCriteria.php
    │ └── ...
    └── Ship
    └── Criterias
    ├── CreatedTodayCriteria.php
    ├── NotNullCriteria.php
    └── ...

    Code Example

    use App\Ship\Parents\Criterias\Criteria as ParentCriteria;
    use Prettus\Repository\Contracts\RepositoryInterface as PrettusRepositoryInterface;

    class IsNullCriteria extends ParentCriteria
    {
    public function __construct(
    private readonly string $field
    ) {
    }

    public function apply($model, PrettusRepositoryInterface $repository)
    {
    return $model->whereNull($this->field);
    }
    }

    Applying Criteria

    A Criteria can be applied to a Repository by using the pushCriteria method.

    public function __construct(
    protected readonly UserRepository $repository
    ) {
    }

    The pushCriteria method accepts an instance of a Criteria class or a string with the Criteria class name.

    public function run()
    {
    $this->repository->pushCriteria(new IsNullCriteria('email'));
    return $this->repository->paginate();
    }

    You can also apply multiple Criterias to a Repository by using the pushCriteria method multiple times.

    public function run()
    {
    $this->repository->pushCriteria(new IsNullCriteria('email'));
    $this->repository->pushCriteria(OrderByNameCriteria::class);
    return $this->repository->paginate();
    }
    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/repository/repositories/index.html b/docs/next/components/optional-components/repository/repositories/index.html index 6060988c8..404cd672f 100644 --- a/docs/next/components/optional-components/repository/repositories/index.html +++ b/docs/next/components/optional-components/repository/repositories/index.html @@ -4,7 +4,7 @@ Repositories | Apiato - + @@ -58,7 +58,7 @@ findById
    getById
    findMany

    pushCriteriaWith

    This method is a wrapper around the pushCriteria method. It accepts data to be passed to the criteria class and allows for easier testing.

    findById

    This method is a wrapper around the find method. But it returns null if the record is not found.

    getById

    This method is a wrapper around the find method. But it throws a NotFoundException if the record is not found.

    findMany

    This method is a wrapper around the find method. But it returns an empty collection if no records are found.

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/seeders/index.html b/docs/next/components/optional-components/seeders/index.html index 5bff2d454..77fa2cdec 100644 --- a/docs/next/components/optional-components/seeders/index.html +++ b/docs/next/components/optional-components/seeders/index.html @@ -4,7 +4,7 @@ Seeders | Apiato - + @@ -25,7 +25,7 @@ You can locate this seeder class at app/Ship/Seeders/SeedDeploymentData.php. Similar to the testing seeder, the deployment seeder is not automatically loaded by Apiato. You can call this seeder and populate your database with production data by executing the following command:

    php artisan apiato:seed-deployment
    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/service-providers/index.html b/docs/next/components/optional-components/service-providers/index.html index 077c09785..24d3a6b05 100644 --- a/docs/next/components/optional-components/service-providers/index.html +++ b/docs/next/components/optional-components/service-providers/index.html @@ -4,7 +4,7 @@ Service Providers | Apiato - + @@ -36,7 +36,7 @@ which makes it available.

    note

    Do note that the App\Ship\Parents\Providers\RouteServiceProvider is a unique case. Because it's required by Apiato, it is registered by the App\Ship\Prviders\ShipProvider and is loaded automatically.

    Service Providers Registration Flow

    If you want to understand the service provider registration process, here is a breakdown of the registration flow.

    Consider the following folder structure:

    app
    ├── Containers
    │ └── Section
    │ ├── ContainerA
    │ │ └── Providers
    │ │ ├── CustomServiceProvider.php ─────────►────────┐
    │ │ ├── EventServiceProvider.php ─────────►────────┤
    │ │ ├── MainServiceProvider.php ◄──registered─in─◄─┘
    │ │ └── ...
    │ └── ContainerB
    │ └── Providers
    │ ├── AnotherCustomServiceProvider.php ────────►────────┐
    │ ├── EventServiceProvider.php ────────►────────┤
    │ ├── MainServiceProvider.php ◄──registered─in─◄─┤
    │ ├── MiddlewareServiceProvider.php ────────►────────┘
    │ └── ...
    └── Ship
    └── Providers
    ├── CustomGeneralServiceProvider.php ────────►────────┐
    ├── RouteServiceProvider.php ────────►────────┤
    ├── ShipProvider.php ◄──registered─in─◄─┘
    └── ...

    The following diagram illustrates the registration flow of service providers in the above folder structure:

    - + \ No newline at end of file diff --git a/docs/next/components/optional-components/tests/index.html b/docs/next/components/optional-components/tests/index.html index 75a4325e2..c4064dfe8 100644 --- a/docs/next/components/optional-components/tests/index.html +++ b/docs/next/components/optional-components/tests/index.html @@ -4,7 +4,7 @@ Tests | Apiato - + @@ -23,7 +23,7 @@ These types of tests provide the most confidence that your system as a whole is functioning as intended.

    Rules

    • All container-specific tests:
      • MUST be placed in the app/Containers/{Section}/{Container}/Tests directory.
      • Functional tests:
        • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional directory.
        • API tests:
          • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional/API directory.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\Functional\ApiTestCase class.
            • MUST extend the App\Containers\{Section}\{Container}\Tests\FunctionalTestCase class.
              • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
        • CLI tests:
          • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Functional/CLI directory.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\Functional\CliTestCase class.
            • MUST extend the App\Containers\{Section}\{Container}\Tests\FunctionalTestCase class.
              • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
      • Unit tests:
        • MUST be placed in the app/Containers/{Section}/{Container}/Tests/Unit directory.
        • MUST extend the App\Containers\{Section}\{Container}\Tests\UnitTestCase class.
          • MUST extend the App\Containers\{Section}\{Container}\Tests\ContainerTestCase class.
        • Directory structure MUST exactly match the Container's directory structure.
    • All Ship Unit tests:
      • MUST be placed in the app/Ship/Tests/Unit directory.
      • MUST extend the App\Ship\Tests\ShipTestCase class.
    • All ContainerTestCases & ShipTestCase MUST extend the App\Ship\Parents\Tests\PhpUnit\TestCase class.
      • The parent extension SHOULD be aliased as ParentTestCase.

    Folder Structure

    app
    ├── Containers
    │ └── Section
    │ └── Container
    │ └── Tests
    │ ├── Functional
    │ │ ├── API
    │ │ │ ├── CreateUserTest.php
    │ │ │ └── ...
    │ │ ├── CLI
    │ │ │ ├── CreateAdminCommandTest.php
    │ │ │ └── ...
    │ │ ├── ApiTestCase.php
    │ │ └── CliTestCase.php
    │ ├── Unit
    │ │ ├── Actions
    │ │ │ ├── CreateUserActionTest.php
    │ │ │ └── ...
    │ │ ├── AnotherDirectory
    │ │ │ ├── ...
    │ │ │ └── ...
    │ │ └── UI
    │ │ ├── API
    │ │ │ ├── Controllers
    │ │ │ ├── Requests
    │ │ │ ├── Transformers
    │ │ │ └── ...
    │ │ └── WEB
    │ │ ├── Controllers
    │ │ ├── Requests
    │ │ ├── Transformers
    │ │ └── ...
    │ ├── ContainerTestCase.php
    │ ├── FunctionalTestCase.php
    │ └── UnitTestCase.php
    └── Ship
    └── Tests
    ├── Unit
    │ ├── UrlRuleTest.php
    │ └── ...
    └── ShipTestCase.php

    Writing Tests

    Unit tests are defined in the same manner as you would define them in Laravel. However, Functional tests follow a distinct approach. Here's an example of how to write functional tests:

    namespace App\Containers\AppSection\User\Tests\Functional\API;

    use App\Containers\AppSection\User\Data\Factories\UserFactory;
    use App\Containers\AppSection\User\Tests\Functional\ApiTestCase;
    use Illuminate\Testing\Fluent\AssertableJson;
    use PHPUnit\Framework\Attributes\CoversNothing;
    use PHPUnit\Framework\Attributes\Group;

    #[Group('user')]
    #[CoversNothing]
    class FindUserByIdTest extends ApiTestCase
    {
    protected string $endpoint = 'get@v1/users/{id}';
    protected bool $auth = true;
    protected array $access = [
    'permissions' => 'search-users',
    'roles' => '',
    ];

    public function testFindUser(): void
    {
    $user = $this->getTestingUser();

    $response = $this->injectId($user->id)->makeCall();

    $response->assertOk();
    $response->assertJson(
    static fn (AssertableJson $json) => $json->has('data')
    ->where('data.id', \Hashids::encode($user->id))
    ->etc()
    );
    }
    }

    To learn more about the properties and methods used, -such as endpoint and makeCall, please read to the following section.

    Functional Test Helpers

    Apiato provides a set of helper methods that you can use to write expressive functional tests.

    Properties

    Certain test helper methods access properties defined in your test class to execute their tasks effectively. +such as endpoint and makeCall, please read to the following section.

    Functional Tests

    Properties

    Certain test helper methods access properties defined in your test class to execute their tasks effectively. Below, we will explore these properties and their corresponding methods:


    endpoint

    The $endpoint property is used to define the endpoints you want to access when making a call using the makeCall method. It is defined as a string in the following format: method@url.

    class FindUserByIdTest extends ApiTestCase
    {
    protected string $endpoint = 'get@v1/profile';

    public function testGetAuthenticatedUser(): void
    {
    $user = $this->getTestingUser();

    $response = $this->makeCall();
    // You can override the "endpoint" property in specific test methods
    // $response = $this->endpoint('get@v1/users')->makeCall();

    $response->assertOk();
    // other assertions...
    }
    }

    auth

    The $auth property is used to determine whether the endpoint being called requires authentication or not in your test class. @@ -77,7 +77,9 @@ specifically for a particular test method.

    $this->auth(false)->makeCall();

    Available Assertions

    Apiato provides a variety of custom assertion methods that you may utilize when testing your application.

    assertModelCastsIsEmpty
    assertDatabaseTable
    getGateMock
    -inIds


    assertModelCastsIsEmpty

    The assertModelCastsIsEmpty method allows you to assert that the $casts property of a model is empty. +assertCriteriaPushedToRepository
    +assertNoCriteriaPushedToRepository
    +allowAddRequestCriteriaInvocation


    assertModelCastsIsEmpty

    The assertModelCastsIsEmpty method allows you to assert that the $casts property of a model is empty. By default, the $casts property of a model includes the id and, if the model is soft deletable, the deleted_at. This method excludes these default values from the assertion.

    Here's an example of how to use assertModelCastsIsEmpty:

    $this->assertModelCastsIsEmpty($model);

    In the code snippet above, $model represents the instance of the model you want to test. @@ -86,11 +88,11 @@ you can pass them as an array to the assertModelCastsIsEmpty method.

    $this->assertModelCastsIsEmpty($model, ['value1', 'value2']);

    In this case, the assertion will ignore the id, deleted_at, value1, and value2 values when verifying the $casts property of the model.

    By using the assertModelCastsIsEmpty method, you can verify that the $casts property of a model does not contain any unexpected values, -ensuring that the model's attributes are not automatically casted.


    assertDatabaseTable

    Available in v12.1.0 and above.

    This method is used +ensuring that the model's attributes are not automatically casted.


    assertDatabaseTable

    Available since Core v8.5.0

    This method is used to verify if the database table specified by table has the expected columns specified in the expectedColumns array. The array should be in the format ['column_name' => 'column_type'], -where the column type is a string representing the expected data type of the column.

    $this->assertDatabaseTable('users', ['id' => 'bigint']);

    getGateMock

    Available in v12.1.0 and above.

    This assertion helps you to test whether the Gate::allows method is invoked with the correct arguments.

    Let's +where the column type is a string representing the expected data type of the column.

    $this->assertDatabaseTable('users', ['id' => 'bigint']);

    getGateMock

    Available since Core v8.5.0

    This assertion helps you to test whether the Gate::allows method is invoked with the correct arguments.

    Let's consider a scenario where a request class utilizes the authorize method to determine whether a user has the necessary permissions to access a particular resource. @@ -100,18 +102,23 @@ The test ensures that the Gate::allows method is invoked with the correct parameters, checking if users have the required permissions to perform updates. If the authorization logic is correctly implemented, this test should pass, -ensuring that only users with the necessary permissions can perform updates.


    inIds

    The inIds method allows you to check if the given hashed ID exists within the provided model collection.

    $hashedId = 'hashed_123';
    $collection = Model::all();

    $isInCollection = $this->inIds($hashedId, $collection);

    By leveraging the inIds method, you can streamline your testing process when working with hashed identifiers, -ensuring that the expected hashed IDs are present within your model collections.

    Deprecation Notice

    This method will be removed in the next major release and will not be available in the test file. -Instead, it will be transformed into a helper function that you can utilize anywhere in your application.

    Faker

    An instance of Faker is automatically provided in every test class, allowing you to generate fake data easily. +ensuring that only users with the necessary permissions can perform updates.


    assertCriteriaPushedToRepository

    Available since Core v8.9.0

    Asserts that a criteria is pushed to a repository.

    In the following example, we want to test +if the UserIsAdminCriteria is pushed to the UserRepository when the ListUsersTask is called with the admin method.

    public function testCanListAdminUsers(): void
    {
    $this->assertCriteriaPushedToRepository(
    UserRepository::class,
    UserIsAdminCriteria::class,
    ['admin' => true],
    );
    $task = app(ListUsersTask::class);

    $task->admin();
    }

    assertNoCriteriaPushedToRepository

    Available since Core v8.9.0

    Asserts that no criteria is pushed to a repository.

    In the following example, we want to test +if no criteria is pushed to the UserRepository when the ListUsersTask's admin method is called with a null value.

    public function testCanListAllUsers(): void
    {
    $this->assertNoCriteriaPushedToRepository(UserRepository::class);
    $task = app(ListUsersTask::class);

    $task->admin(null);
    }

    allowAddRequestCriteriaInvocation

    Available since Core v8.9.0

    Allow addRequestCriteria method invocation on the repository mock. +This is particularly useful when you want to test a repository that uses the +RequestCriteria.

    public function testCanListAdminUsers(): void
    {
    $repositoryMock = $this->assertCriteriaPushedToRepository(
    UserRepository::class,
    UserIsAdminCriteria::class,
    ['admin' => true],
    );
    $this->allowAddRequestCriteriaInvocation($repositoryMock);
    $task = app(ListUsersTask::class);

    $task->admin();
    }

    Faker

    An instance of Faker is automatically provided in every test class, allowing you to generate fake data easily. You can access it using $this->faker.

    Deprecation Notice

    This feature is deprecated and will be removed in the next major release. -You should use the Laravel fake helper function instead.

    Create Live Testing Data

    To test your application using live testing data, +You should use the Laravel fake helper function instead.

    Test Helper Methods

    Apiato provides a variety of helper methods that you may utilize when testing your application.

    createSpyWithRepository
    +inIds


    createSpyWithRepository

    Available since Core v8.9.0

    This method is useful when you want to test if a repository method is called within an Action, SubAction or a Task.

    In the following example, +we want to test if the run method of the CreateUserTask is called within the CreateUserAction.

    public function testCanCreateUser(): void
    {
    $data = [
    'email' => 'gandalf@the.grey',
    'password' => 'you-shall-not-pass',
    ];
    $taskSpy = $this->createSpyWithRepository(CreateUserTask::class, UserRepository::class);
    $action = app(CreateUserAction::class);

    $action->run($data);

    $taskSpy->shouldHaveReceived('run')->once()->with($data);
    }

    inIds

    The inIds method allows you to check if the given hashed ID exists within the provided model collection.

    $hashedId = 'hashed_123';
    $collection = Model::all();

    $isInCollection = $this->inIds($hashedId, $collection);

    By leveraging the inIds method, you can streamline your testing process when working with hashed identifiers, +ensuring that the expected hashed IDs are present within your model collections.

    Deprecation Notice

    This method will be removed in the next major release and will not be available in test classes.

    Create Live Testing Data

    To test your application using live testing data, such as creating items in an inventory, you can utilize the feature designed specifically for this purpose. It allows for the automatic generation of testing data, which can be helpful during staging or when real people are testing your application.

    To create your live testing data, navigate to the app/Ship/Seeder/SeedTestingData.php seeder class. Within this class, you can define the logic and data generation process for your testing data.

    Once you have defined your testing data, you can run the following command in your terminal:

    php artisan apiato:seed-test

    This command triggers the seeding process specifically for testing data, -populating your application with the generated data.

    - +populating your application with the generated data.

    + \ No newline at end of file diff --git a/docs/next/components/optional-components/values/index.html b/docs/next/components/optional-components/values/index.html index b4403363d..e64e86ccb 100644 --- a/docs/next/components/optional-components/values/index.html +++ b/docs/next/components/optional-components/values/index.html @@ -4,7 +4,7 @@ Values | Apiato - + @@ -15,7 +15,7 @@ Additionally, they do not possess functionality or modify any state; their sole purpose is to hold data.

    Value Objects are particularly well-suited for use with Laravel attributes casting, which allows us to cast a Value Object to a specific type, enabling seamless integration with Eloquent models and database operations.

    To generate new values you may use the apiato:generate:value interactive command:

    php artisan apiato:generate:value

    Rules

    • All container-specific Values MUST be placed in the app/Containers/{section}/{container}/Values directory.
    • All general Values MUST be placed in the app/Ship/Values directory.
    • All Values MUST extend the App\Ship\Parents\Values\Value class.
      • The parent extension SHOULD be aliased as ParentValue.

    Folder Structure

    app
    └── Containers
    └── Section
    └── Container
    └── Values
    ├── Output.php
    ├── Region.php
    └── ...

    Code Example

    class Location extends Value
    {
    public function __construct(
    public float $latitude,
    public float $longitude,
    ) {
    }
    }
    - + \ No newline at end of file diff --git a/docs/next/consulting/index.html b/docs/next/consulting/index.html index a1a79d1ae..baa8e7894 100644 --- a/docs/next/consulting/index.html +++ b/docs/next/consulting/index.html @@ -4,13 +4,13 @@ Consulting | Apiato - +
    - + \ No newline at end of file diff --git a/docs/next/framework-features/api-versioning/index.html b/docs/next/framework-features/api-versioning/index.html index da16c31e9..b348ea9b2 100644 --- a/docs/next/framework-features/api-versioning/index.html +++ b/docs/next/framework-features/api-versioning/index.html @@ -4,13 +4,13 @@ API Versioning | Apiato - +
    - + \ No newline at end of file diff --git a/docs/next/framework-features/code-generator/index.html b/docs/next/framework-features/code-generator/index.html index 4677460c2..327965acb 100644 --- a/docs/next/framework-features/code-generator/index.html +++ b/docs/next/framework-features/code-generator/index.html @@ -4,7 +4,7 @@ Code Generator | Apiato - + @@ -22,7 +22,7 @@ However, it is crucial to adhere to one essential rule:

    • The name of the file and the folder structure in app/Ship/Generators/CustomStubs MUST exactly match those in vendor/apiato/core/Generator/Stubs.

    To illustrate the process, let's assume you want to customize the creation of an action. Follow these steps:

    1. Locate the action.stub file in vendor/apiato/core/Generator/Stubs/actions.
    2. Copy the action.stub file and paste it into the app/Ship/Generators/CustomStubs/actions directory.
    3. Make the desired changes to the copied action.stub file according to your requirements.

    By completing these steps, whenever you run the php artisan apiato:generate:action command, your customized stub file will be employed instead of the default one, applying your modifications to the generated action files.

    - + \ No newline at end of file diff --git a/docs/next/framework-features/etag/index.html b/docs/next/framework-features/etag/index.html index 3ca38ec5d..e7fc8e01f 100644 --- a/docs/next/framework-features/etag/index.html +++ b/docs/next/framework-features/etag/index.html @@ -4,7 +4,7 @@ Etag | Apiato - + @@ -12,7 +12,7 @@
    Version: Next 🚧

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. It is one of several mechanisms that HTTP provides for Web cache validation, which allows a client to make conditional requests.

    You can read more about this feature in the Requests documentation.

    - + \ No newline at end of file diff --git a/docs/next/framework-features/index.html b/docs/next/framework-features/index.html index 45ee53f53..b19cca3d5 100644 --- a/docs/next/framework-features/index.html +++ b/docs/next/framework-features/index.html @@ -4,13 +4,13 @@ Framework Features | Apiato - + - + \ No newline at end of file diff --git a/docs/next/framework-features/profiler/index.html b/docs/next/framework-features/profiler/index.html index e361f6d74..c41ff0b42 100644 --- a/docs/next/framework-features/profiler/index.html +++ b/docs/next/framework-features/profiler/index.html @@ -4,7 +4,7 @@ Profiler | Apiato - + @@ -17,7 +17,7 @@ Apiato employs the Apiato\Core\Middlewares\Http\ProfilerMiddleware to include the profiling data in the response.

    The profiler feature is initially disabled by default. To enable it, you should edit the .env file and set DEBUGBAR_ENABLED=true.

    To customize and manage the profiler response, you'll need to make adjustments in the configuration file located at app/Ship/Configs/debugbar.php.

    The following is an example of the profiler response:

    {
    "_profiler": {
    "__meta": {
    "id": "X167f293230e3457f1bbd95d9c82aba4a",
    "datetime": "2017-09-22 18:45:27",
    "utime": 1506105927.799299,
    "method": "GET",
    "uri": "/",
    "ip": "172.20.0.1"
    },
    "messages": {
    "count": 0,
    "messages": []
    },
    "time": {
    "start": 1506105922.742068,
    "end": 1506105927.799333,
    "duration": 5.057265043258667,
    "duration_str": "5.06s",
    "measures": [
    {
    "label": "Booting",
    "start": 1506105922.742068,
    "relative_start": 0,
    "end": 1506105923.524004,
    "relative_end": 1506105923.524004,
    "duration": 0.7819359302520752,
    "duration_str": "781.94ms",
    "params": [],
    "collector": null
    },
    {
    "label": "Application",
    "start": 1506105923.535343,
    "relative_start": 0.7932748794555664,
    "end": 1506105927.799336,
    "relative_end": 0.00000286102294921875,
    "duration": 4.26399302482605,
    "duration_str": "4.26s",
    "params": [],
    "collector": null
    }
    ]
    },
    "memory": {
    "peak_usage": 13234248,
    "peak_usage_str": "12.62MB"
    },
    "exceptions": {
    "count": 0,
    "exceptions": []
    },
    "route": {
    "uri": "GET /",
    "middleware": "api, throttle:30,1",
    "domain": "http://api.apiato.test",
    "as": "apis_root_page",
    "controller": "App\\Containers\\Welcome\\UI\\API\\Controllers\\Controller@apiRoot",
    "namespace": "App\\Containers\\Welcome\\UI\\API\\Controllers",
    "prefix": "/",
    "where": [],
    "file": "app/Containers/Welcome/UI/API/Controllers/Controller.php:20-25"
    },
    "queries": {
    "nb_statements": 0,
    "nb_failed_statements": 0,
    "accumulated_duration": 0,
    "accumulated_duration_str": "0μs",
    "statements": []
    },
    "logs": {
    "count": 3,
    "messages": [
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694807
    },
    {
    "message": "...",
    "message_html": null,
    "is_string": false,
    "label": "error",
    "time": 1506105927.694811
    },
    {
    "message": "[2017-09-18 17:38:15] testing.INFO: New User registration. ID = 970ylqvaogmxnbdr | Email = apiato@mail.test. Thank you for signing up.\n</div>\n</body>\n</html>\n \n",
    "message_html": null,
    "is_string": false,
    "label": "info",
    "time": 1506105927.694812
    }
    ]
    },
    "auth": {
    "guards": {
    "web": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]",
    "api": "array:2 [\n \"name\" => \"Guest\"\n \"user\" => array:1 [\n \"guest\" => true\n ]\n]"
    },
    "names": ""
    },
    "gate": {
    "count": 0,
    "messages": []
    }
    }
    }
    - + \ No newline at end of file diff --git a/docs/next/framework-features/rate-limiting/index.html b/docs/next/framework-features/rate-limiting/index.html index c2dfba2ba..35ee98df5 100644 --- a/docs/next/framework-features/rate-limiting/index.html +++ b/docs/next/framework-features/rate-limiting/index.html @@ -4,7 +4,7 @@ Rate Limiting | Apiato - + @@ -17,7 +17,7 @@ You can disable it on specific endpoints or globally.

    On a specific endpoint

    Rate limiting can be disabled by removing the api middleware from an endpoint using withoutMiddleware('throttle:api') method. Read More.

    Globally

    To disable rate limiting completely, set GLOBAL_API_RATE_LIMIT_ENABLED to false in the .env file.

    - + \ No newline at end of file diff --git a/docs/next/framework-features/rbac/index.html b/docs/next/framework-features/rbac/index.html index 2f19debaf..05c94292a 100644 --- a/docs/next/framework-features/rbac/index.html +++ b/docs/next/framework-features/rbac/index.html @@ -4,7 +4,7 @@ Role Based Access Control | Apiato - + @@ -12,7 +12,7 @@
    - + \ No newline at end of file diff --git a/docs/next/getting-started/best-practices/index.html b/docs/next/getting-started/best-practices/index.html index 845523eb6..3c7de51de 100644 --- a/docs/next/getting-started/best-practices/index.html +++ b/docs/next/getting-started/best-practices/index.html @@ -4,7 +4,7 @@ Best Practices | Apiato - + @@ -45,7 +45,7 @@ This gives maintainers of the API enough information to understand the problem that’s occurred. We don’t want errors to bring down our system, so we can leave them unhandled, which means that the API consumer has to handle them.

    Common error HTTP status codes include:

    • 200 OK Server successfully returned the requested data.
    • 201 CREATED Server successfully created or modified the requested resource.
    • 204 NO CONTENT Server successfully deleted the requested resource.
    • 400 INVALID REQUEST The request was invalid or cannot be served. The exact error should be explained in the error payload. E.g. „The JSON is not valid“.
    • 401 UNAUTHORIZED The request requires an user authentication.
    • 403 FORBIDDEN The server understood the request, but is refusing it or the access is not allowed.
    • 404 NOT FOUND There is no resource behind the URI.
    • 422 Unprocessable Entity Should be used if the server cannot process the enitity, e.g. if an image cannot be formatted or mandatory fields are missing in the payload.
    • 500 INTERNAL SERVER ERROR Internal Server Error.
    • 502 BAD GATEWAY Server received an invalid response from the upstream server while trying to fulfill the request.
    • 503 SERVICE UNAVAILABLE Service unavailable.

    Naming Conventions For Routes & Actions

    • ListResources: to fetch all resources.
    • FindResourceByID: to search for a single resource by its unique identifier.
    • CreateResource: to create a new resource.
    • UpdateResource: to update/edit existing resource.
    • DeleteResource: to delete a resource.
    - + \ No newline at end of file diff --git a/docs/next/getting-started/customized-laravel-components/index.html b/docs/next/getting-started/customized-laravel-components/index.html index bbe87d018..04330ec5a 100644 --- a/docs/next/getting-started/customized-laravel-components/index.html +++ b/docs/next/getting-started/customized-laravel-components/index.html @@ -4,14 +4,14 @@ Customized Laravel Components | Apiato - +
    Version: Next 🚧

    Customized Laravel Components

    Apiato provides a refined organization for Laravel default class locations. Here, you can find the default Laravel components and their corresponding locations within Apiato.

    Kernels

    • Http Kernel is moved from app/Http to app/Ship/Kernels and renamed to HttpKernel.

    • Console Kernel is moved from app/Console to app/Ship/Kernels and renamed to ConsoleKernel.

    Middlewares

    • Middlewares are moved from app/Http/Middleware to app/Ship/Middlewares.

    Handler

    • Exception Handler is moved from app/Exceptions to app/Ship/Exceptions/Handlers and renamed to ExceptionsHandler.

    Providers

    • For information about the new locations of Providers, please refer to this link.

    Routes

    Web and API

    Apiato introduces a new approach to route organization and does not use the default routes/web.php and routes/api.php files. Therefore, you won't find these files in Apiato. To learn more, please visit this link.

    Channels

    • The channels.php file has been relocated from routes to app/Ship/Broadcasts.

    Console

    • The console.php file has been moved from routes to app/Ship/Commands and renamed to closures.php.
    - + \ No newline at end of file diff --git a/docs/next/getting-started/installation/index.html b/docs/next/getting-started/installation/index.html index 24fd48632..bb49a0faf 100644 --- a/docs/next/getting-started/installation/index.html +++ b/docs/next/getting-started/installation/index.html @@ -4,7 +4,7 @@ Installation | Apiato - + @@ -42,7 +42,7 @@ assuming you are using the default Subdomain and API Version Prefix configuration, you should be able to access the following URLs and see the following results:

    Web (Browser)

    API (HTTP Client)

    Next Steps

    Now that you have created your Apiato project, you may be wondering what to learn next. If you're looking for a place to start, you should check out the following resources:

    - + \ No newline at end of file diff --git a/docs/next/pacakges/documentation/index.html b/docs/next/pacakges/documentation/index.html index a5561cafd..f6a67f49f 100644 --- a/docs/next/pacakges/documentation/index.html +++ b/docs/next/pacakges/documentation/index.html @@ -4,7 +4,7 @@ Documentation | Apiato - + @@ -15,7 +15,7 @@ access-private-docs-permission values in documentation config. By default, users need access-private-docs permission to access private docs.

    Edit Default Generated Values in Templates

    Apiato by defaults generates 2 API documentations, each one has its own apidoc.json file. Both can be modified from the Documentation Container in app/Containers/Vendor/Documentation/ApiDocJs and need Source code modification.

    Edit the Documentation Header

    The header is usually the Overview of your API. It contains Info about authenticating users, making requests, responses, potential errors, rate limiting, pagination, query parameters and anything you want.

    All this information is written in app/Containers/Vendor/Documentation/ApiDocJs/shared/header.template.en.md file, and the same file is used as header for both private and public documentations.

    To edit its content you need to modify its source code and open the markdown file in any markdown editor and edit it.

    You will notice some variables like {{rate-limit}} and {{token-expires}}. Those are replaced when running apiato:apidoc with real values from your application configuration files.

    Feel free to extend them to include more info about your API from the app/Containers/Vendor/Documentation/Tasks/RenderTemplatesTask.php class.

    Localization for Documentation Header

    Default, the documentation title is in English en localization.

    See which locales are supported by going in app/Containers/Vendor/Documentation/ApiDocJs/shared

    There will be some header.template.{locale}.md files in the folder.

    You can change the language by adding APIDOC_LOCALE=ru to the .env file.

    If you didn't find a file with your locale, you can create it. You need to modify its source code and create new file like header.template.cn.md

    - + \ No newline at end of file diff --git a/docs/next/pacakges/index.html b/docs/next/pacakges/index.html index 666c56f40..d6cd4a20d 100644 --- a/docs/next/pacakges/index.html +++ b/docs/next/pacakges/index.html @@ -4,7 +4,7 @@ Overview | Apiato - + @@ -21,7 +21,7 @@ that allows installing/updating containers.
  • You must provide the key extra.apiato.container.name. This key indicates the name of the folder (e.g., container) when installing the package to the app/Containers/Vendor directory. In the shown example, the container would be installed to app/Containers/Vendor/Foo.
  • - + \ No newline at end of file diff --git a/docs/next/pacakges/localization/index.html b/docs/next/pacakges/localization/index.html index 3b4778585..7689ebdd0 100644 --- a/docs/next/pacakges/localization/index.html +++ b/docs/next/pacakges/localization/index.html @@ -4,7 +4,7 @@ Localization | Apiato - + @@ -41,7 +41,7 @@ language in this specific language (e.g., locale_name => Deutsch). Furthermore, the language name is outputted in the applications default name (e.g., configured in app.locale). This would result in default_name => German.

    The same applies to the regions that are defined (e.g., de-DE). Consequently, this results in locale_name => Deutschland and default_name = Germany.

    Tests

    To change the default language in your tests requests. You can set the env language in the phpunit.xml file.

    - + \ No newline at end of file diff --git a/docs/next/pacakges/social-authentication/index.html b/docs/next/pacakges/social-authentication/index.html index 39b299dba..f0ae3b33a 100644 --- a/docs/next/pacakges/social-authentication/index.html +++ b/docs/next/pacakges/social-authentication/index.html @@ -4,7 +4,7 @@ Social Authentication | Apiato - + @@ -19,7 +19,7 @@ to get the oauth info and user data respectively.

    Social Authentication Container Customization

    You can customize this container by publishing its config and modifying its values

    php artisan vendor:publish

    Config file will be copied to app/Ship/Configs/vendor-socialAuth.php

    Support new Auth Provider

    1. Publish the configs
    2. Create your new auth provider by implementing the App\Containers\Vendor\SocialAuth\Contracts\SocialAuthProvider contract.
      To get an idea about how to implement your own provider you can check out supported providers here app/Containers/Vendor/SocialAuth/SocialAuthProviders.
    3. Add your new provider to providers array in the vendor-socialAuth config.
        'providers' => [
    ...
    'something' => Location\Of\Your\Provider\SomthingSocialAuthProvider::class,
    ],

    Changing default used Repository, Transformer & DB user table name

    This container depends on Apiato default user repository, transformer & database user table name. If you changed those defaults you can update and provide them in the configs.

    - + \ No newline at end of file diff --git a/docs/next/prologue/contribution-guide/index.html b/docs/next/prologue/contribution-guide/index.html index 370d98483..85e89b997 100644 --- a/docs/next/prologue/contribution-guide/index.html +++ b/docs/next/prologue/contribution-guide/index.html @@ -4,7 +4,7 @@ Contribution Guide | Apiato - + @@ -37,7 +37,7 @@ after pull requests are merged. This allows us to focus on the content of the contribution and not the code style.

    Code of Conduct

    The Apiato code of conduct is derived from the Ruby code of conduct. Any violations of the code of conduct may be reported to Mohammad Alavi (mohammad.alavi1990@gmail.com):

    • Participants will be tolerant of opposing views.
    • Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
    • When interpreting the words and actions of others, participants should always assume good intentions.
    • Behavior that can be reasonably considered harassment will not be tolerated.
    - + \ No newline at end of file diff --git a/docs/next/prologue/release-notes/index.html b/docs/next/prologue/release-notes/index.html index e3533b5ae..b031fbf3e 100644 --- a/docs/next/prologue/release-notes/index.html +++ b/docs/next/prologue/release-notes/index.html @@ -4,7 +4,7 @@ Release Notes | Apiato - + @@ -17,7 +17,7 @@ since major releases of Apiato do include breaking changes. However, we strive to always ensure you may update to a new major release in one day or less.

    Support Policy

    For all Apiato releases, bug fixes are provided for 18 months and security fixes are provided for 2 years.

    VersionPHP (*)ReleaseBug Fixes UntilSecurity Fixes Until
    107.3 - 8.1April 25th, 2021October 25th, 2022April 25th, 2023
    118.0 - 8.2April 27th, 2022October 27th, 2023April 27th, 2024
    128.1 - 8.2June 4th, 2023December 4th, 2024June 4th, 2025
    138.2Q1 2024August 5th, 2025February 3rd, 2026

    (*) Supported PHP versions

    Apiato 12

    Full Changelog: https://github.com/apiato/apiato/compare/v11.3.2...v12.0.0

    PHP 8.1

    Apiato 12.x requires a minimum PHP version of 8.1.

    Breaking Changes

    • Upgraded to Laravel v10 (All Laravel files (e.g. configs, .env, etc...) are now synced with the latest Laravel changes)
    • Updated Composer dependencies to their latest version
    • Laravel Passport route registration & customization has changed. Passport routes are now reside in a dedicated route file (Instead of registering them in the provider).
    • Middleware $routeMiddleware field is renamed to $middlewareAliases
    • Trimmed down the TestCase by removing some useless traits including:
    TestsMockHelperTrait
    TestsResponseHelperTrait
    • encode() method return value has changed -> In case of unencodable value (e.g. null), now returns null instead of ''
    • decode() method return value has changed -> In case of undecodable value (e.g. null), now returns null instead of []
    • StateKeeperTrait is removed from Request

    None Breaking Changes

    • Everything is refactored to use constructor injection instead of directly using the Service Container like so app(CreateUserByCredentialsTask::class)->run()
    • Added more tests and refactored the rest
    • Switched to invokable controllers
    \\ from
    Route::get('profile', [GetAuthenticatedUserController::class, 'getAuthenticatedUser']);
    \\ to
    Route::get('profile', GetAuthenticatedUserController::class);
    • All rotues are moved into the private documentation. e.g. RefreshProxyForWebClient.v1.public.php -> RefreshProxyForWebClient.v1.private.php
    • Added some getter methods to the Request including:
    withUrlParameters()
    getAccessArray()
    getDecodeArray()
    getUrlParametersArray()
    • Added a TestAssertionHelperTrait to the TestCase which provides some usefull assertions

    Bug Fixes

    • withMeta() method on ResponseTrait now correctly includes added meta data
    • Calling invokable controllers from routes #174
    • Exception when try to generate an WEB CRUD Controller from generator #171
    • PHP 8.1 warning on passing null to explode #176
    - + \ No newline at end of file diff --git a/docs/next/prologue/upgrade-guide/index.html b/docs/next/prologue/upgrade-guide/index.html index 58f38be4b..d85515f7f 100644 --- a/docs/next/prologue/upgrade-guide/index.html +++ b/docs/next/prologue/upgrade-guide/index.html @@ -4,13 +4,13 @@ Upgrade Guide | Apiato - +
    Version: Next 🚧

    Upgrade Guide

    Upgrade from 11.0 to 12.0

    WORK IN PROGRESS

    - + \ No newline at end of file diff --git a/docs/next/security/authentication/index.html b/docs/next/security/authentication/index.html index 98f4ba2cc..15d1604e8 100644 --- a/docs/next/security/authentication/index.html +++ b/docs/next/security/authentication/index.html @@ -4,7 +4,7 @@ Authentication | Apiato - + @@ -67,7 +67,7 @@ you can navigate to the app/Ship/Providers/RouteServiceProvider.php file and update the LOGIN constant.

    Passing The Access Token

    When calling routes that are protected by Passport, your application's API consumers should specify their access token as a Bearer token in the Authorization header of their request. For example, when using the Guzzle HTTP library:

    use Illuminate\Support\Facades\Http;

    $response = Http::withHeaders([
    'Accept' => 'application/json',
    'Authorization' => 'Bearer '.$accessToken,
    ])->get('http://api.apiato.test/v1/users');

    return $response->json();

    Configuration

    Most of the configuration is done in the app/Ships/Configs/apiato.php file.

    Social Authentication

    For Social Authentication visit the Social Authentication page.

    - + \ No newline at end of file diff --git a/docs/next/security/authorization/index.html b/docs/next/security/authorization/index.html index a160dcd67..177517834 100644 --- a/docs/next/security/authorization/index.html +++ b/docs/next/security/authorization/index.html @@ -4,7 +4,7 @@ Authorization | Apiato - + @@ -19,7 +19,7 @@ you ensure that unauthorized users are denied access before any further processing takes place.

    Default Roles & Permissions

    Apiato comes with some default Roles and Permissions. You can find them in app/Containers/AppSection/Authorization/Data/Seeders. You can use them as a starting point, or delete them and create your own.

    Code Example

    Protecting the delete user endpoint with delete-users permission:

    use App\Ship\Parents\Requests\Request as ParentRequest;

    class DeleteUserRequest extends ParentRequest
    {
    protected array $access = [
    'permissions' => 'delete-users',
    'roles' => '',
    ];

    public function authorize(): bool
    {
    return $this->check([
    'hasAccess',
    ]);
    }
    }

    Authorization failed JSON response:

    {
    "message": "This action is unauthorized.",
    "errors": []
    }
    - + \ No newline at end of file diff --git a/docs/next/security/email-varification/index.html b/docs/next/security/email-varification/index.html index 763bb7920..a84077353 100644 --- a/docs/next/security/email-varification/index.html +++ b/docs/next/security/email-varification/index.html @@ -4,7 +4,7 @@ Email Verification | Apiato - + @@ -22,7 +22,7 @@ when using a load balancer, set the protected $proxies = '*' in the app/Ship/Middlewares/TrustProxies.php or customize it according to your needs.

    - + \ No newline at end of file diff --git a/docs/next/security/hash-id/index.html b/docs/next/security/hash-id/index.html index e000f62ad..ebe8c78bb 100644 --- a/docs/next/security/hash-id/index.html +++ b/docs/next/security/hash-id/index.html @@ -4,7 +4,7 @@ Hash ID | Apiato - + @@ -20,7 +20,7 @@ You can set the HASH_ID_KEY in the .env file to any random string. Apiato defaults to the APP_KEY should this not be set.

    danger

    The HASH_ID_KEY acts as the salt during hashing of the ID. This should never be changed in production as it renders all previously generated IDs impossible to decode.

    Route Model Binding

    Laravel Route Model Binding feature is supported out of the box and Apiato will automatically decode the ID for you.

    - + \ No newline at end of file diff --git a/docs/next/security/password-reset/index.html b/docs/next/security/password-reset/index.html index 9414b4c4a..23788b6c8 100644 --- a/docs/next/security/password-reset/index.html +++ b/docs/next/security/password-reset/index.html @@ -4,7 +4,7 @@ Password Reset | Apiato - + @@ -15,7 +15,7 @@ in the allowed-reset-password-urls array within the appSection-authentication configuration.

    Routing

    To request a password reset link, call the /password/forgot endpoint with the user's email address.

    Resetting The Password

    To reset the user's password, call the /password/reset endpoint with the user's email address, new password, and password reset token.

    Process Flow

    1. Add your web app's password reset page URL, for example, https://myapp.com/password/reset, to the allowed-reset-password-urls array within the appSection-authentication configuration.

    2. Call the /password/forgot endpoint with a reset URL of your choice, which should correspond to one of the URLs in the allowed-reset-password-urls array. This endpoint will send the user an email containing a link like this:
      https://myapp.com/password/resetd?email=mohammad.alavi1990@gmail.com&token=51f8d80182f3785648c9b9dc7162719d158fc418b3cca86c14963638ec83d663

    3. When the user clicks on that link, they will be directed to your front-end app's password reset page. From there, you can collect the user's new password and make a call to the /password/reset endpoint with all the required fields to complete the password reset.

    - + \ No newline at end of file diff --git a/docs/next/security/registration/index.html b/docs/next/security/registration/index.html index 670b16474..d79d2134a 100644 --- a/docs/next/security/registration/index.html +++ b/docs/next/security/registration/index.html @@ -4,13 +4,13 @@ Registration | Apiato - +
    Version: Next 🚧

    Registration

    Apiato supports two default user registration methods:

    1. Register by Credentials
    2. Register by Social Account

    You can also extend these methods or add new ones to customize your registration process.

    Register by Credentials

    To register a new user, send a POST request to the /register endpoint.

    api.apiato.test/v1/register
    tip

    Don't forget to add Accept: application/json header to your request.

    The /register endpoint expects a string email address and a string password field.

    {
    "email": "gandalg@the.grey",
    "password": "password"
    }

    You should receive a response similar to the following:

    {
    "data": {
    "object": "User",
    "id": "XbPW7awNkzl83LD6",
    "name": null,
    "email": "john@doe.com",
    "email_verified_at": null,
    "gender": null,
    "birth": null
    },
    "meta": {
    "include": [
    "roles",
    "permissions"
    ],
    "custom": []
    }
    }

    Register by Social Account

    (Facebook, Twitter, Google, etc...)

    Checkout the Social Authentication documentation.

    - + \ No newline at end of file diff --git a/docs/next/tags/action/index.html b/docs/next/tags/action/index.html index 1d797e5ca..a4e0b0dec 100644 --- a/docs/next/tags/action/index.html +++ b/docs/next/tags/action/index.html @@ -4,13 +4,13 @@ 9 docs tagged with "action" | Apiato - +

    9 docs tagged with "action"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Commands

    Apiato commands are just Laravel Commands,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Requests

    Requests components are a way to interact with the current HTTP request

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/next/tags/api-versioning/index.html b/docs/next/tags/api-versioning/index.html index 04347fd59..8bb1c924d 100644 --- a/docs/next/tags/api-versioning/index.html +++ b/docs/next/tags/api-versioning/index.html @@ -4,13 +4,13 @@ One doc tagged with "api-versioning" | Apiato - +

    One doc tagged with "api-versioning"

    View All Tags

    API Versioning

    Apiato provides a streamlined approach to implementing API versioning within your application.

    - + \ No newline at end of file diff --git a/docs/next/tags/architecture/index.html b/docs/next/tags/architecture/index.html index 32a0285d4..fb17c8d56 100644 --- a/docs/next/tags/architecture/index.html +++ b/docs/next/tags/architecture/index.html @@ -4,13 +4,13 @@ 5 docs tagged with "architecture" | Apiato - +

    5 docs tagged with "architecture"

    View All Tags

    Container

    Containers are at the core of Apiato.

    Porto

    Porto is a modern software architectural pattern that offers developers a comprehensive set of guidelines,

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/next/tags/authorization/index.html b/docs/next/tags/authorization/index.html index 3af01f52e..d8abb54da 100644 --- a/docs/next/tags/authorization/index.html +++ b/docs/next/tags/authorization/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "authorization" | Apiato - +

    2 docs tagged with "authorization"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Policies

    Apiato policies are just Laravel Policies,

    - + \ No newline at end of file diff --git a/docs/next/tags/code-generator/index.html b/docs/next/tags/code-generator/index.html index 68a5f28b8..02ed55de0 100644 --- a/docs/next/tags/code-generator/index.html +++ b/docs/next/tags/code-generator/index.html @@ -4,13 +4,13 @@ One doc tagged with "code-generator" | Apiato - +

    One doc tagged with "code-generator"

    View All Tags

    Code Generator

    Apiato comes with a powerful code generator that can help you to generate all the boilerplate code for your containers.

    - + \ No newline at end of file diff --git a/docs/next/tags/command/index.html b/docs/next/tags/command/index.html index f92a21d16..c5d4f0f92 100644 --- a/docs/next/tags/command/index.html +++ b/docs/next/tags/command/index.html @@ -4,13 +4,13 @@ One doc tagged with "command" | Apiato - +

    One doc tagged with "command"

    View All Tags

    Commands

    Apiato commands are just Laravel Commands,

    - + \ No newline at end of file diff --git a/docs/next/tags/component/index.html b/docs/next/tags/component/index.html index 6579e615c..fee5b1907 100644 --- a/docs/next/tags/component/index.html +++ b/docs/next/tags/component/index.html @@ -4,13 +4,13 @@ 29 docs tagged with "component" | Apiato - +

    29 docs tagged with "component"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Commands

    Apiato commands are just Laravel Commands,

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    Controllers

    Controllers are tasked with two primary responsibilities:

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    Factories

    Apiato factories are just Laravel Factories,

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Models

    Models are responsible for representing the data of the application

    Notifications

    Apiato notifications are just Laravel Notifications,

    Policies

    Apiato policies are just Laravel Policies,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Seeders

    Apiato seeders are just Laravel Seeders,

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    Tests

    Apiato is built with testing in mind.

    Values

    Value Objects are short names for known "Value Objects",

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/next/tags/config/index.html b/docs/next/tags/config/index.html index 7abba7e0d..b3f9fab3a 100644 --- a/docs/next/tags/config/index.html +++ b/docs/next/tags/config/index.html @@ -4,13 +4,13 @@ One doc tagged with "config" | Apiato - +

    One doc tagged with "config"

    View All Tags

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    - + \ No newline at end of file diff --git a/docs/next/tags/container/index.html b/docs/next/tags/container/index.html index 03231d0ad..981121bec 100644 --- a/docs/next/tags/container/index.html +++ b/docs/next/tags/container/index.html @@ -4,13 +4,13 @@ One doc tagged with "container" | Apiato - +

    One doc tagged with "container"

    View All Tags

    Container

    Containers are at the core of Apiato.

    - + \ No newline at end of file diff --git a/docs/next/tags/controller/index.html b/docs/next/tags/controller/index.html index 62ac91468..e9ca346bb 100644 --- a/docs/next/tags/controller/index.html +++ b/docs/next/tags/controller/index.html @@ -4,13 +4,13 @@ 8 docs tagged with "controller" | Apiato - +

    8 docs tagged with "controller"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/next/tags/criteria/index.html b/docs/next/tags/criteria/index.html index 584fdc935..823320ff7 100644 --- a/docs/next/tags/criteria/index.html +++ b/docs/next/tags/criteria/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "criteria" | Apiato - +

    2 docs tagged with "criteria"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    - + \ No newline at end of file diff --git a/docs/next/tags/etag/index.html b/docs/next/tags/etag/index.html index 234d242e9..66ee87efe 100644 --- a/docs/next/tags/etag/index.html +++ b/docs/next/tags/etag/index.html @@ -4,13 +4,13 @@ One doc tagged with "etag" | Apiato - +

    One doc tagged with "etag"

    View All Tags

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.

    - + \ No newline at end of file diff --git a/docs/next/tags/event/index.html b/docs/next/tags/event/index.html index 03e64052e..c27c1a06b 100644 --- a/docs/next/tags/event/index.html +++ b/docs/next/tags/event/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "event" | Apiato - +

    2 docs tagged with "event"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    - + \ No newline at end of file diff --git a/docs/next/tags/exception/index.html b/docs/next/tags/exception/index.html index d49d5b2c6..458fe53a4 100644 --- a/docs/next/tags/exception/index.html +++ b/docs/next/tags/exception/index.html @@ -4,13 +4,13 @@ One doc tagged with "exception" | Apiato - +

    One doc tagged with "exception"

    View All Tags

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    - + \ No newline at end of file diff --git a/docs/next/tags/factory/index.html b/docs/next/tags/factory/index.html index 4ccbd42d4..c5f9b5c36 100644 --- a/docs/next/tags/factory/index.html +++ b/docs/next/tags/factory/index.html @@ -4,13 +4,13 @@ One doc tagged with "factory" | Apiato - +

    One doc tagged with "factory"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    - + \ No newline at end of file diff --git a/docs/next/tags/framework-feature/index.html b/docs/next/tags/framework-feature/index.html index c0801023e..b72552933 100644 --- a/docs/next/tags/framework-feature/index.html +++ b/docs/next/tags/framework-feature/index.html @@ -4,13 +4,13 @@ 6 docs tagged with "framework-feature" | Apiato - +

    6 docs tagged with "framework-feature"

    View All Tags

    API Versioning

    Apiato provides a streamlined approach to implementing API versioning within your application.

    Code Generator

    Apiato comes with a powerful code generator that can help you to generate all the boilerplate code for your containers.

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.

    Profiler

    Profiling is a crucial aspect of optimizing your application's performance

    - + \ No newline at end of file diff --git a/docs/next/tags/helper/index.html b/docs/next/tags/helper/index.html index 51b6dac55..b8e4495c6 100644 --- a/docs/next/tags/helper/index.html +++ b/docs/next/tags/helper/index.html @@ -4,13 +4,13 @@ One doc tagged with "helper" | Apiato - +

    One doc tagged with "helper"

    View All Tags

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    - + \ No newline at end of file diff --git a/docs/next/tags/index.html b/docs/next/tags/index.html index d5199487a..b871bf518 100644 --- a/docs/next/tags/index.html +++ b/docs/next/tags/index.html @@ -4,13 +4,13 @@ Tags | Apiato - + - + \ No newline at end of file diff --git a/docs/next/tags/job/index.html b/docs/next/tags/job/index.html index ff5950c98..626a9d1af 100644 --- a/docs/next/tags/job/index.html +++ b/docs/next/tags/job/index.html @@ -4,13 +4,13 @@ One doc tagged with "job" | Apiato - +

    One doc tagged with "job"

    View All Tags

    Jobs

    Apiato jobs are just Laravel Jobs,

    - + \ No newline at end of file diff --git a/docs/next/tags/lifecycle/index.html b/docs/next/tags/lifecycle/index.html index b23c4eb8c..ef22d2df0 100644 --- a/docs/next/tags/lifecycle/index.html +++ b/docs/next/tags/lifecycle/index.html @@ -4,13 +4,13 @@ One doc tagged with "lifecycle" | Apiato - +

    One doc tagged with "lifecycle"

    View All Tags

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/next/tags/listener/index.html b/docs/next/tags/listener/index.html index f0a3db691..a8a01033e 100644 --- a/docs/next/tags/listener/index.html +++ b/docs/next/tags/listener/index.html @@ -4,13 +4,13 @@ One doc tagged with "listener" | Apiato - +

    One doc tagged with "listener"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    - + \ No newline at end of file diff --git a/docs/next/tags/mail/index.html b/docs/next/tags/mail/index.html index 725469941..9524d1078 100644 --- a/docs/next/tags/mail/index.html +++ b/docs/next/tags/mail/index.html @@ -4,13 +4,13 @@ One doc tagged with "mail" | Apiato - +

    One doc tagged with "mail"

    View All Tags

    Mail

    Apiato mails are just Laravel Mails,

    - + \ No newline at end of file diff --git a/docs/next/tags/main-component/index.html b/docs/next/tags/main-component/index.html index 77e9a99a9..05bbb9c70 100644 --- a/docs/next/tags/main-component/index.html +++ b/docs/next/tags/main-component/index.html @@ -4,13 +4,13 @@ 10 docs tagged with "main-component" | Apiato - +

    10 docs tagged with "main-component"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    Models

    Models are responsible for representing the data of the application

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/next/tags/middleware/index.html b/docs/next/tags/middleware/index.html index 78c2fa1d6..2336c1447 100644 --- a/docs/next/tags/middleware/index.html +++ b/docs/next/tags/middleware/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "middleware" | Apiato - +

    2 docs tagged with "middleware"

    View All Tags

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    - + \ No newline at end of file diff --git a/docs/next/tags/migration/index.html b/docs/next/tags/migration/index.html index 11e096363..048f59124 100644 --- a/docs/next/tags/migration/index.html +++ b/docs/next/tags/migration/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "migration" | Apiato - +

    2 docs tagged with "migration"

    View All Tags

    Migrations

    Apiato migrations are just Laravel Migrations,

    Seeders

    Apiato seeders are just Laravel Seeders,

    - + \ No newline at end of file diff --git a/docs/next/tags/model/index.html b/docs/next/tags/model/index.html index 4b3b27020..4c77790f8 100644 --- a/docs/next/tags/model/index.html +++ b/docs/next/tags/model/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "model" | Apiato - +

    4 docs tagged with "model"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    Models

    Models are responsible for representing the data of the application

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/next/tags/notification/index.html b/docs/next/tags/notification/index.html index ca8c3e90f..09ca0130c 100644 --- a/docs/next/tags/notification/index.html +++ b/docs/next/tags/notification/index.html @@ -4,13 +4,13 @@ One doc tagged with "notification" | Apiato - +

    One doc tagged with "notification"

    View All Tags

    Notifications

    Apiato notifications are just Laravel Notifications,

    - + \ No newline at end of file diff --git a/docs/next/tags/optional-component/index.html b/docs/next/tags/optional-component/index.html index 45eac7610..eac5ecbc1 100644 --- a/docs/next/tags/optional-component/index.html +++ b/docs/next/tags/optional-component/index.html @@ -4,13 +4,13 @@ 18 docs tagged with "optional-component" | Apiato - +

    18 docs tagged with "optional-component"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Commands

    Apiato commands are just Laravel Commands,

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Factories

    Apiato factories are just Laravel Factories,

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Notifications

    Apiato notifications are just Laravel Notifications,

    Policies

    Apiato policies are just Laravel Policies,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Seeders

    Apiato seeders are just Laravel Seeders,

    Tests

    Apiato is built with testing in mind.

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/next/tags/policy/index.html b/docs/next/tags/policy/index.html index 7ad544e75..88eb51d4e 100644 --- a/docs/next/tags/policy/index.html +++ b/docs/next/tags/policy/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "policy" | Apiato - +

    2 docs tagged with "policy"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Policies

    Apiato policies are just Laravel Policies,

    - + \ No newline at end of file diff --git a/docs/next/tags/porto/index.html b/docs/next/tags/porto/index.html index c113a8956..aab13e60d 100644 --- a/docs/next/tags/porto/index.html +++ b/docs/next/tags/porto/index.html @@ -4,13 +4,13 @@ 5 docs tagged with "porto" | Apiato - +

    5 docs tagged with "porto"

    View All Tags

    Container

    Containers are at the core of Apiato.

    Porto

    Porto is a modern software architectural pattern that offers developers a comprehensive set of guidelines,

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/next/tags/profiler/index.html b/docs/next/tags/profiler/index.html index 0298f4f88..f8b5abe4a 100644 --- a/docs/next/tags/profiler/index.html +++ b/docs/next/tags/profiler/index.html @@ -4,13 +4,13 @@ One doc tagged with "profiler" | Apiato - +

    One doc tagged with "profiler"

    View All Tags

    Profiler

    Profiling is a crucial aspect of optimizing your application's performance

    - + \ No newline at end of file diff --git a/docs/next/tags/queue/index.html b/docs/next/tags/queue/index.html index d17c9533b..5675cbd99 100644 --- a/docs/next/tags/queue/index.html +++ b/docs/next/tags/queue/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "queue" | Apiato - +

    3 docs tagged with "queue"

    View All Tags

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Notifications

    Apiato notifications are just Laravel Notifications,

    - + \ No newline at end of file diff --git a/docs/next/tags/rate-limiting/index.html b/docs/next/tags/rate-limiting/index.html index be40ca36b..eb0ceeb55 100644 --- a/docs/next/tags/rate-limiting/index.html +++ b/docs/next/tags/rate-limiting/index.html @@ -4,13 +4,13 @@ One doc tagged with "rate-limiting" | Apiato - +

    One doc tagged with "rate-limiting"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/next/tags/repository/index.html b/docs/next/tags/repository/index.html index f45c8fe31..0c807dc82 100644 --- a/docs/next/tags/repository/index.html +++ b/docs/next/tags/repository/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "repository" | Apiato - +

    3 docs tagged with "repository"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Models

    Models are responsible for representing the data of the application

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    - + \ No newline at end of file diff --git a/docs/next/tags/request/index.html b/docs/next/tags/request/index.html index b6c8572a6..ad4233a77 100644 --- a/docs/next/tags/request/index.html +++ b/docs/next/tags/request/index.html @@ -4,13 +4,13 @@ 8 docs tagged with "request" | Apiato - +

    8 docs tagged with "request"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Policies

    Apiato policies are just Laravel Policies,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    - + \ No newline at end of file diff --git a/docs/next/tags/response/index.html b/docs/next/tags/response/index.html index 4c4c4bfd0..577a1e6e2 100644 --- a/docs/next/tags/response/index.html +++ b/docs/next/tags/response/index.html @@ -4,13 +4,13 @@ One doc tagged with "response" | Apiato - +

    One doc tagged with "response"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/next/tags/role-based-access-control/index.html b/docs/next/tags/role-based-access-control/index.html index 0e66c37c1..f03d7efd2 100644 --- a/docs/next/tags/role-based-access-control/index.html +++ b/docs/next/tags/role-based-access-control/index.html @@ -4,13 +4,13 @@ One doc tagged with "role-based-access-control" | Apiato - +

    One doc tagged with "role-based-access-control"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/next/tags/route/index.html b/docs/next/tags/route/index.html index c6f0aaca4..8334a539c 100644 --- a/docs/next/tags/route/index.html +++ b/docs/next/tags/route/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "route" | Apiato - +

    4 docs tagged with "route"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    - + \ No newline at end of file diff --git a/docs/next/tags/seeder/index.html b/docs/next/tags/seeder/index.html index 36a84f0c5..65bf91db2 100644 --- a/docs/next/tags/seeder/index.html +++ b/docs/next/tags/seeder/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "seeder" | Apiato - +

    3 docs tagged with "seeder"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Seeders

    Apiato seeders are just Laravel Seeders,

    - + \ No newline at end of file diff --git a/docs/next/tags/service-provider/index.html b/docs/next/tags/service-provider/index.html index a9427fdc9..47262baf0 100644 --- a/docs/next/tags/service-provider/index.html +++ b/docs/next/tags/service-provider/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "service-provider" | Apiato - +

    3 docs tagged with "service-provider"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    - + \ No newline at end of file diff --git a/docs/next/tags/sub-action/index.html b/docs/next/tags/sub-action/index.html index 4023f482f..72d702ba1 100644 --- a/docs/next/tags/sub-action/index.html +++ b/docs/next/tags/sub-action/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "sub-action" | Apiato - +

    2 docs tagged with "sub-action"

    View All Tags

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/next/tags/task/index.html b/docs/next/tags/task/index.html index 1b6116b08..ba245349e 100644 --- a/docs/next/tags/task/index.html +++ b/docs/next/tags/task/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "task" | Apiato - +

    4 docs tagged with "task"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/next/tags/test/index.html b/docs/next/tags/test/index.html index 41505cf2f..7009ed44e 100644 --- a/docs/next/tags/test/index.html +++ b/docs/next/tags/test/index.html @@ -4,13 +4,13 @@ One doc tagged with "test" | Apiato - +

    One doc tagged with "test"

    View All Tags

    Tests

    Apiato is built with testing in mind.

    - + \ No newline at end of file diff --git a/docs/next/tags/testing/index.html b/docs/next/tags/testing/index.html index 557c06987..3b775f9f7 100644 --- a/docs/next/tags/testing/index.html +++ b/docs/next/tags/testing/index.html @@ -4,13 +4,13 @@ One doc tagged with "testing" | Apiato - +

    One doc tagged with "testing"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    - + \ No newline at end of file diff --git a/docs/next/tags/transformer/index.html b/docs/next/tags/transformer/index.html index 806487814..8ae0301d2 100644 --- a/docs/next/tags/transformer/index.html +++ b/docs/next/tags/transformer/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "transformer" | Apiato - +

    2 docs tagged with "transformer"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    - + \ No newline at end of file diff --git a/docs/next/tags/value/index.html b/docs/next/tags/value/index.html index ff5bcf36c..655a8f827 100644 --- a/docs/next/tags/value/index.html +++ b/docs/next/tags/value/index.html @@ -4,13 +4,13 @@ One doc tagged with "value" | Apiato - +

    One doc tagged with "value"

    View All Tags

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/next/tags/view/index.html b/docs/next/tags/view/index.html index abf45dc2e..5614827d5 100644 --- a/docs/next/tags/view/index.html +++ b/docs/next/tags/view/index.html @@ -4,13 +4,13 @@ One doc tagged with "view" | Apiato - +

    One doc tagged with "view"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    - + \ No newline at end of file diff --git a/docs/pacakges/documentation/index.html b/docs/pacakges/documentation/index.html index d397cda4d..b7d6b7a56 100644 --- a/docs/pacakges/documentation/index.html +++ b/docs/pacakges/documentation/index.html @@ -4,7 +4,7 @@ Documentation | Apiato - + @@ -15,7 +15,7 @@ access-private-docs-permission values in documentation config. By default, users need access-private-docs permission to access private docs.

    Edit Default Generated Values in Templates

    Apiato by defaults generates 2 API documentations, each one has its own apidoc.json file. Both can be modified from the Documentation Container in app/Containers/Vendor/Documentation/ApiDocJs and need Source code modification.

    Edit the Documentation Header

    The header is usually the Overview of your API. It contains Info about authenticating users, making requests, responses, potential errors, rate limiting, pagination, query parameters and anything you want.

    All this information is written in app/Containers/Vendor/Documentation/ApiDocJs/shared/header.template.en.md file, and the same file is used as header for both private and public documentations.

    To edit its content you need to modify its source code and open the markdown file in any markdown editor and edit it.

    You will notice some variables like {{rate-limit}} and {{token-expires}}. Those are replaced when running apiato:apidoc with real values from your application configuration files.

    Feel free to extend them to include more info about your API from the app/Containers/Vendor/Documentation/Tasks/RenderTemplatesTask.php class.

    Localization for Documentation Header

    Default, the documentation title is in English en localization.

    See which locales are supported by going in app/Containers/Vendor/Documentation/ApiDocJs/shared

    There will be some header.template.{locale}.md files in the folder.

    You can change the language by adding APIDOC_LOCALE=ru to the .env file.

    If you didn't find a file with your locale, you can create it. You need to modify its source code and create new file like header.template.cn.md

    - + \ No newline at end of file diff --git a/docs/pacakges/index.html b/docs/pacakges/index.html index 1c61083dc..8d6dac154 100644 --- a/docs/pacakges/index.html +++ b/docs/pacakges/index.html @@ -4,7 +4,7 @@ Overview | Apiato - + @@ -21,7 +21,7 @@ that allows installing/updating containers.
  • You must provide the key extra.apiato.container.name. This key indicates the name of the folder (e.g., container) when installing the package to the app/Containers/Vendor directory. In the shown example, the container would be installed to app/Containers/Vendor/Foo.
  • - + \ No newline at end of file diff --git a/docs/pacakges/localization/index.html b/docs/pacakges/localization/index.html index 44caad4de..c9a5faca3 100644 --- a/docs/pacakges/localization/index.html +++ b/docs/pacakges/localization/index.html @@ -4,7 +4,7 @@ Localization | Apiato - + @@ -41,7 +41,7 @@ language in this specific language (e.g., locale_name => Deutsch). Furthermore, the language name is outputted in the applications default name (e.g., configured in app.locale). This would result in default_name => German.

    The same applies to the regions that are defined (e.g., de-DE). Consequently, this results in locale_name => Deutschland and default_name = Germany.

    Tests

    To change the default language in your tests requests. You can set the env language in the phpunit.xml file.

    - + \ No newline at end of file diff --git a/docs/pacakges/social-authentication/index.html b/docs/pacakges/social-authentication/index.html index 184c56e2a..b4a02bc19 100644 --- a/docs/pacakges/social-authentication/index.html +++ b/docs/pacakges/social-authentication/index.html @@ -4,7 +4,7 @@ Social Authentication | Apiato - + @@ -19,7 +19,7 @@ to get the oauth info and user data respectively.

    Social Authentication Container Customization

    You can customize this container by publishing its config and modifying its values

    php artisan vendor:publish

    Config file will be copied to app/Ship/Configs/vendor-socialAuth.php

    Support new Auth Provider

    1. Publish the configs
    2. Create your new auth provider by implementing the App\Containers\Vendor\SocialAuth\Contracts\SocialAuthProvider contract.
      To get an idea about how to implement your own provider you can check out supported providers here app/Containers/Vendor/SocialAuth/SocialAuthProviders.
    3. Add your new provider to providers array in the vendor-socialAuth config.
        'providers' => [
    ...
    'something' => Location\Of\Your\Provider\SomthingSocialAuthProvider::class,
    ],

    Changing default used Repository, Transformer & DB user table name

    This container depends on Apiato default user repository, transformer & database user table name. If you changed those defaults you can update and provide them in the configs.

    - + \ No newline at end of file diff --git a/docs/prologue/contribution-guide/index.html b/docs/prologue/contribution-guide/index.html index dc9894bfa..393d4b5d8 100644 --- a/docs/prologue/contribution-guide/index.html +++ b/docs/prologue/contribution-guide/index.html @@ -4,7 +4,7 @@ Contribution Guide | Apiato - + @@ -37,7 +37,7 @@ after pull requests are merged. This allows us to focus on the content of the contribution and not the code style.

    Code of Conduct

    The Apiato code of conduct is derived from the Ruby code of conduct. Any violations of the code of conduct may be reported to Mohammad Alavi (mohammad.alavi1990@gmail.com):

    • Participants will be tolerant of opposing views.
    • Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
    • When interpreting the words and actions of others, participants should always assume good intentions.
    • Behavior that can be reasonably considered harassment will not be tolerated.
    - + \ No newline at end of file diff --git a/docs/prologue/release-notes/index.html b/docs/prologue/release-notes/index.html index 473626967..2a846d663 100644 --- a/docs/prologue/release-notes/index.html +++ b/docs/prologue/release-notes/index.html @@ -4,7 +4,7 @@ Release Notes | Apiato - + @@ -17,7 +17,7 @@ since major releases of Apiato do include breaking changes. However, we strive to always ensure you may update to a new major release in one day or less.

    Support Policy

    For all Apiato releases, bug fixes are provided for 18 months and security fixes are provided for 2 years.

    VersionPHP (*)ReleaseBug Fixes UntilSecurity Fixes Until
    107.3 - 8.1April 25th, 2021October 25th, 2022April 25th, 2023
    118.0 - 8.2April 27th, 2022October 27th, 2023April 27th, 2024
    128.1 - 8.2June 4th, 2023December 4th, 2024June 4th, 2025
    138.2Q1 2024August 5th, 2025February 3rd, 2026

    (*) Supported PHP versions

    Apiato 12

    Full Changelog: https://github.com/apiato/apiato/compare/v11.3.2...v12.0.0

    PHP 8.1

    Apiato 12.x requires a minimum PHP version of 8.1.

    Breaking Changes

    • Upgraded to Laravel v10 (All Laravel files (e.g. configs, .env, etc...) are now synced with the latest Laravel changes)
    • Updated Composer dependencies to their latest version
    • Laravel Passport route registration & customization has changed. Passport routes are now reside in a dedicated route file (Instead of registering them in the provider).
    • Middleware $routeMiddleware field is renamed to $middlewareAliases
    • Trimmed down the TestCase by removing some useless traits including:
    TestsMockHelperTrait
    TestsResponseHelperTrait
    • encode() method return value has changed -> In case of unencodable value (e.g. null), now returns null instead of ''
    • decode() method return value has changed -> In case of undecodable value (e.g. null), now returns null instead of []
    • StateKeeperTrait is removed from Request

    None Breaking Changes

    • Everything is refactored to use constructor injection instead of directly using the Service Container like so app(CreateUserByCredentialsTask::class)->run()
    • Added more tests and refactored the rest
    • Switched to invokable controllers
    \\ from
    Route::get('profile', [GetAuthenticatedUserController::class, 'getAuthenticatedUser']);
    \\ to
    Route::get('profile', GetAuthenticatedUserController::class);
    • All rotues are moved into the private documentation. e.g. RefreshProxyForWebClient.v1.public.php -> RefreshProxyForWebClient.v1.private.php
    • Added some getter methods to the Request including:
    withUrlParameters()
    getAccessArray()
    getDecodeArray()
    getUrlParametersArray()
    • Added a TestAssertionHelperTrait to the TestCase which provides some usefull assertions

    Bug Fixes

    • withMeta() method on ResponseTrait now correctly includes added meta data
    • Calling invokable controllers from routes #174
    • Exception when try to generate an WEB CRUD Controller from generator #171
    • PHP 8.1 warning on passing null to explode #176
    - + \ No newline at end of file diff --git a/docs/prologue/upgrade-guide/index.html b/docs/prologue/upgrade-guide/index.html index 0b7b5b0a2..f758e60d2 100644 --- a/docs/prologue/upgrade-guide/index.html +++ b/docs/prologue/upgrade-guide/index.html @@ -4,13 +4,13 @@ Upgrade Guide | Apiato - +
    - + \ No newline at end of file diff --git a/docs/security/authentication/index.html b/docs/security/authentication/index.html index 6c6638dcc..47f104f88 100644 --- a/docs/security/authentication/index.html +++ b/docs/security/authentication/index.html @@ -4,7 +4,7 @@ Authentication | Apiato - + @@ -67,7 +67,7 @@ you can navigate to the app/Ship/Providers/RouteServiceProvider.php file and update the LOGIN constant.

    Passing The Access Token

    When calling routes that are protected by Passport, your application's API consumers should specify their access token as a Bearer token in the Authorization header of their request. For example, when using the Guzzle HTTP library:

    use Illuminate\Support\Facades\Http;

    $response = Http::withHeaders([
    'Accept' => 'application/json',
    'Authorization' => 'Bearer '.$accessToken,
    ])->get('http://api.apiato.test/v1/users');

    return $response->json();

    Configuration

    Most of the configuration is done in the app/Ships/Configs/apiato.php file.

    Social Authentication

    For Social Authentication visit the Social Authentication page.

    - + \ No newline at end of file diff --git a/docs/security/authorization/index.html b/docs/security/authorization/index.html index 552cd11e1..741b6792c 100644 --- a/docs/security/authorization/index.html +++ b/docs/security/authorization/index.html @@ -4,7 +4,7 @@ Authorization | Apiato - + @@ -19,7 +19,7 @@ you ensure that unauthorized users are denied access before any further processing takes place.

    Default Roles & Permissions

    Apiato comes with some default Roles and Permissions. You can find them in app/Containers/AppSection/Authorization/Data/Seeders. You can use them as a starting point, or delete them and create your own.

    Code Example

    Protecting the delete user endpoint with delete-users permission:

    use App\Ship\Parents\Requests\Request as ParentRequest;

    class DeleteUserRequest extends ParentRequest
    {
    protected array $access = [
    'permissions' => 'delete-users',
    'roles' => '',
    ];

    public function authorize(): bool
    {
    return $this->check([
    'hasAccess',
    ]);
    }
    }

    Authorization failed JSON response:

    {
    "message": "This action is unauthorized.",
    "errors": []
    }
    - + \ No newline at end of file diff --git a/docs/security/email-varification/index.html b/docs/security/email-varification/index.html index ddac1cf23..d39d2d84d 100644 --- a/docs/security/email-varification/index.html +++ b/docs/security/email-varification/index.html @@ -4,7 +4,7 @@ Email Verification | Apiato - + @@ -22,7 +22,7 @@ when using a load balancer, set the protected $proxies = '*' in the app/Ship/Middlewares/TrustProxies.php or customize it according to your needs.

    - + \ No newline at end of file diff --git a/docs/security/hash-id/index.html b/docs/security/hash-id/index.html index ad22352ca..6c690b179 100644 --- a/docs/security/hash-id/index.html +++ b/docs/security/hash-id/index.html @@ -4,7 +4,7 @@ Hash ID | Apiato - + @@ -20,7 +20,7 @@ You can set the HASH_ID_KEY in the .env file to any random string. Apiato defaults to the APP_KEY should this not be set.

    danger

    The HASH_ID_KEY acts as the salt during hashing of the ID. This should never be changed in production as it renders all previously generated IDs impossible to decode.

    Route Model Binding

    Laravel Route Model Binding feature is supported out of the box and Apiato will automatically decode the ID for you.

    - + \ No newline at end of file diff --git a/docs/security/password-reset/index.html b/docs/security/password-reset/index.html index 64ec57ac9..d77971920 100644 --- a/docs/security/password-reset/index.html +++ b/docs/security/password-reset/index.html @@ -4,7 +4,7 @@ Password Reset | Apiato - + @@ -15,7 +15,7 @@ in the allowed-reset-password-urls array within the appSection-authentication configuration.

    Routing

    To request a password reset link, call the /password/forgot endpoint with the user's email address.

    Resetting The Password

    To reset the user's password, call the /password/reset endpoint with the user's email address, new password, and password reset token.

    Process Flow

    1. Add your web app's password reset page URL, for example, https://myapp.com/password/reset, to the allowed-reset-password-urls array within the appSection-authentication configuration.

    2. Call the /password/forgot endpoint with a reset URL of your choice, which should correspond to one of the URLs in the allowed-reset-password-urls array. This endpoint will send the user an email containing a link like this:
      https://myapp.com/password/resetd?email=mohammad.alavi1990@gmail.com&token=51f8d80182f3785648c9b9dc7162719d158fc418b3cca86c14963638ec83d663

    3. When the user clicks on that link, they will be directed to your front-end app's password reset page. From there, you can collect the user's new password and make a call to the /password/reset endpoint with all the required fields to complete the password reset.

    - + \ No newline at end of file diff --git a/docs/security/registration/index.html b/docs/security/registration/index.html index f82c4991a..c1c98177c 100644 --- a/docs/security/registration/index.html +++ b/docs/security/registration/index.html @@ -4,13 +4,13 @@ Registration | Apiato - +
    Version: 12.x

    Registration

    Apiato supports two default user registration methods:

    1. Register by Credentials
    2. Register by Social Account

    You can also extend these methods or add new ones to customize your registration process.

    Register by Credentials

    To register a new user, send a POST request to the /register endpoint.

    api.apiato.test/v1/register
    tip

    Don't forget to add Accept: application/json header to your request.

    The /register endpoint expects a string email address and a string password field.

    {
    "email": "gandalg@the.grey",
    "password": "password"
    }

    You should receive a response similar to the following:

    {
    "data": {
    "object": "User",
    "id": "XbPW7awNkzl83LD6",
    "name": null,
    "email": "john@doe.com",
    "email_verified_at": null,
    "gender": null,
    "birth": null
    },
    "meta": {
    "include": [
    "roles",
    "permissions"
    ],
    "custom": []
    }
    }

    Register by Social Account

    (Facebook, Twitter, Google, etc...)

    Checkout the Social Authentication documentation.

    - + \ No newline at end of file diff --git a/docs/tags/action/index.html b/docs/tags/action/index.html index 549d6cef7..d8e3b8781 100644 --- a/docs/tags/action/index.html +++ b/docs/tags/action/index.html @@ -4,13 +4,13 @@ 9 docs tagged with "action" | Apiato - +

    9 docs tagged with "action"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Commands

    Apiato commands are just Laravel Commands,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Requests

    Requests components are a way to interact with the current HTTP request

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/tags/api-versioning/index.html b/docs/tags/api-versioning/index.html index 050398700..67bb7d09d 100644 --- a/docs/tags/api-versioning/index.html +++ b/docs/tags/api-versioning/index.html @@ -4,13 +4,13 @@ One doc tagged with "api-versioning" | Apiato - +

    One doc tagged with "api-versioning"

    View All Tags

    API Versioning

    Apiato provides a streamlined approach to implementing API versioning within your application.

    - + \ No newline at end of file diff --git a/docs/tags/architecture/index.html b/docs/tags/architecture/index.html index c4baad20c..c77046bba 100644 --- a/docs/tags/architecture/index.html +++ b/docs/tags/architecture/index.html @@ -4,13 +4,13 @@ 5 docs tagged with "architecture" | Apiato - +

    5 docs tagged with "architecture"

    View All Tags

    Container

    Containers are at the core of Apiato.

    Porto

    Porto is a modern software architectural pattern that offers developers a comprehensive set of guidelines,

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/tags/authorization/index.html b/docs/tags/authorization/index.html index 1de4ee960..b4d710acf 100644 --- a/docs/tags/authorization/index.html +++ b/docs/tags/authorization/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "authorization" | Apiato - +

    2 docs tagged with "authorization"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Policies

    Apiato policies are just Laravel Policies,

    - + \ No newline at end of file diff --git a/docs/tags/code-generator/index.html b/docs/tags/code-generator/index.html index e782fb7b5..0ff818a4c 100644 --- a/docs/tags/code-generator/index.html +++ b/docs/tags/code-generator/index.html @@ -4,13 +4,13 @@ One doc tagged with "code-generator" | Apiato - +

    One doc tagged with "code-generator"

    View All Tags

    Code Generator

    Apiato comes with a powerful code generator that can help you to generate all the boilerplate code for your containers.

    - + \ No newline at end of file diff --git a/docs/tags/command/index.html b/docs/tags/command/index.html index 114064b39..cf340e4c4 100644 --- a/docs/tags/command/index.html +++ b/docs/tags/command/index.html @@ -4,13 +4,13 @@ One doc tagged with "command" | Apiato - +

    One doc tagged with "command"

    View All Tags

    Commands

    Apiato commands are just Laravel Commands,

    - + \ No newline at end of file diff --git a/docs/tags/component/index.html b/docs/tags/component/index.html index f11ad3697..fc1f639c2 100644 --- a/docs/tags/component/index.html +++ b/docs/tags/component/index.html @@ -4,13 +4,13 @@ 29 docs tagged with "component" | Apiato - +

    29 docs tagged with "component"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Commands

    Apiato commands are just Laravel Commands,

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    Controllers

    Controllers are tasked with two primary responsibilities:

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    Factories

    Apiato factories are just Laravel Factories,

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Models

    Models are responsible for representing the data of the application

    Notifications

    Apiato notifications are just Laravel Notifications,

    Policies

    Apiato policies are just Laravel Policies,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Seeders

    Apiato seeders are just Laravel Seeders,

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    Tests

    Apiato is built with testing in mind.

    Values

    Value Objects are short names for known "Value Objects",

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/tags/config/index.html b/docs/tags/config/index.html index 9d41244ab..f15ac4a12 100644 --- a/docs/tags/config/index.html +++ b/docs/tags/config/index.html @@ -4,13 +4,13 @@ One doc tagged with "config" | Apiato - +

    One doc tagged with "config"

    View All Tags

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    - + \ No newline at end of file diff --git a/docs/tags/container/index.html b/docs/tags/container/index.html index 5cb52d907..f098f8b82 100644 --- a/docs/tags/container/index.html +++ b/docs/tags/container/index.html @@ -4,13 +4,13 @@ One doc tagged with "container" | Apiato - +

    One doc tagged with "container"

    View All Tags

    Container

    Containers are at the core of Apiato.

    - + \ No newline at end of file diff --git a/docs/tags/controller/index.html b/docs/tags/controller/index.html index ab3142bea..19d1abbf7 100644 --- a/docs/tags/controller/index.html +++ b/docs/tags/controller/index.html @@ -4,13 +4,13 @@ 8 docs tagged with "controller" | Apiato - +

    8 docs tagged with "controller"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/tags/criteria/index.html b/docs/tags/criteria/index.html index e2dee23e6..2cbdf7e1c 100644 --- a/docs/tags/criteria/index.html +++ b/docs/tags/criteria/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "criteria" | Apiato - +

    2 docs tagged with "criteria"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    - + \ No newline at end of file diff --git a/docs/tags/etag/index.html b/docs/tags/etag/index.html index 4b8357bda..6f117c418 100644 --- a/docs/tags/etag/index.html +++ b/docs/tags/etag/index.html @@ -4,13 +4,13 @@ One doc tagged with "etag" | Apiato - +

    One doc tagged with "etag"

    View All Tags

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.

    - + \ No newline at end of file diff --git a/docs/tags/event/index.html b/docs/tags/event/index.html index a030941c6..dfac8e174 100644 --- a/docs/tags/event/index.html +++ b/docs/tags/event/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "event" | Apiato - +

    2 docs tagged with "event"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    - + \ No newline at end of file diff --git a/docs/tags/exception/index.html b/docs/tags/exception/index.html index 9953ea954..a8d43d219 100644 --- a/docs/tags/exception/index.html +++ b/docs/tags/exception/index.html @@ -4,13 +4,13 @@ One doc tagged with "exception" | Apiato - +

    One doc tagged with "exception"

    View All Tags

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    - + \ No newline at end of file diff --git a/docs/tags/factory/index.html b/docs/tags/factory/index.html index fac3bd9d8..17abcdab9 100644 --- a/docs/tags/factory/index.html +++ b/docs/tags/factory/index.html @@ -4,13 +4,13 @@ One doc tagged with "factory" | Apiato - +

    One doc tagged with "factory"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    - + \ No newline at end of file diff --git a/docs/tags/framework-feature/index.html b/docs/tags/framework-feature/index.html index 6d320dc48..d7c0aa6c4 100644 --- a/docs/tags/framework-feature/index.html +++ b/docs/tags/framework-feature/index.html @@ -4,13 +4,13 @@ 6 docs tagged with "framework-feature" | Apiato - +

    6 docs tagged with "framework-feature"

    View All Tags

    API Versioning

    Apiato provides a streamlined approach to implementing API versioning within your application.

    Code Generator

    Apiato comes with a powerful code generator that can help you to generate all the boilerplate code for your containers.

    Etag

    The ETag or entity tag is part of HTTP, the protocol for the World Wide Web.

    Profiler

    Profiling is a crucial aspect of optimizing your application's performance

    - + \ No newline at end of file diff --git a/docs/tags/helper/index.html b/docs/tags/helper/index.html index 491fe326b..6921becfe 100644 --- a/docs/tags/helper/index.html +++ b/docs/tags/helper/index.html @@ -4,13 +4,13 @@ One doc tagged with "helper" | Apiato - +

    One doc tagged with "helper"

    View All Tags

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    - + \ No newline at end of file diff --git a/docs/tags/index.html b/docs/tags/index.html index 5c4a65e6c..a7b56cefa 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -4,13 +4,13 @@ Tags | Apiato - + - + \ No newline at end of file diff --git a/docs/tags/job/index.html b/docs/tags/job/index.html index d9fad7da6..d089b529f 100644 --- a/docs/tags/job/index.html +++ b/docs/tags/job/index.html @@ -4,13 +4,13 @@ One doc tagged with "job" | Apiato - +

    One doc tagged with "job"

    View All Tags

    Jobs

    Apiato jobs are just Laravel Jobs,

    - + \ No newline at end of file diff --git a/docs/tags/lifecycle/index.html b/docs/tags/lifecycle/index.html index c0db4df2c..c6548aafb 100644 --- a/docs/tags/lifecycle/index.html +++ b/docs/tags/lifecycle/index.html @@ -4,13 +4,13 @@ One doc tagged with "lifecycle" | Apiato - +

    One doc tagged with "lifecycle"

    View All Tags

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/tags/listener/index.html b/docs/tags/listener/index.html index 34ec48eed..b9b15255e 100644 --- a/docs/tags/listener/index.html +++ b/docs/tags/listener/index.html @@ -4,13 +4,13 @@ One doc tagged with "listener" | Apiato - +

    One doc tagged with "listener"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    - + \ No newline at end of file diff --git a/docs/tags/mail/index.html b/docs/tags/mail/index.html index 31ae2359e..1d77a6d7d 100644 --- a/docs/tags/mail/index.html +++ b/docs/tags/mail/index.html @@ -4,13 +4,13 @@ One doc tagged with "mail" | Apiato - +

    One doc tagged with "mail"

    View All Tags

    Mail

    Apiato mails are just Laravel Mails,

    - + \ No newline at end of file diff --git a/docs/tags/main-component/index.html b/docs/tags/main-component/index.html index 1ffa76625..816bf311a 100644 --- a/docs/tags/main-component/index.html +++ b/docs/tags/main-component/index.html @@ -4,13 +4,13 @@ 10 docs tagged with "main-component" | Apiato - +

    10 docs tagged with "main-component"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Controllers

    Controllers are tasked with two primary responsibilities:

    Exceptions

    Exceptions are used to handle errors and exceptions in the application.

    Models

    Models are responsible for representing the data of the application

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    Views

    Views offer a convenient mechanism for organizing HTML content in separate files.

    - + \ No newline at end of file diff --git a/docs/tags/middleware/index.html b/docs/tags/middleware/index.html index 1b9d157ff..3071b8819 100644 --- a/docs/tags/middleware/index.html +++ b/docs/tags/middleware/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "middleware" | Apiato - +

    2 docs tagged with "middleware"

    View All Tags

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    - + \ No newline at end of file diff --git a/docs/tags/migration/index.html b/docs/tags/migration/index.html index dbb9f8560..7d4705f32 100644 --- a/docs/tags/migration/index.html +++ b/docs/tags/migration/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "migration" | Apiato - +

    2 docs tagged with "migration"

    View All Tags

    Migrations

    Apiato migrations are just Laravel Migrations,

    Seeders

    Apiato seeders are just Laravel Seeders,

    - + \ No newline at end of file diff --git a/docs/tags/model/index.html b/docs/tags/model/index.html index fc2907ffa..de24af826 100644 --- a/docs/tags/model/index.html +++ b/docs/tags/model/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "model" | Apiato - +

    4 docs tagged with "model"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    Models

    Models are responsible for representing the data of the application

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/tags/notification/index.html b/docs/tags/notification/index.html index c9d370331..f8dc8eca9 100644 --- a/docs/tags/notification/index.html +++ b/docs/tags/notification/index.html @@ -4,13 +4,13 @@ One doc tagged with "notification" | Apiato - +

    One doc tagged with "notification"

    View All Tags

    Notifications

    Apiato notifications are just Laravel Notifications,

    - + \ No newline at end of file diff --git a/docs/tags/optional-component/index.html b/docs/tags/optional-component/index.html index 14d209cf9..73eda3bcf 100644 --- a/docs/tags/optional-component/index.html +++ b/docs/tags/optional-component/index.html @@ -4,13 +4,13 @@ 18 docs tagged with "optional-component" | Apiato - +

    18 docs tagged with "optional-component"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Commands

    Apiato commands are just Laravel Commands,

    Configs

    Apiato configs are just Laravel configs, and they function in the exact same way as Laravel configs.

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Factories

    Apiato factories are just Laravel Factories,

    Helpers

    You have the option to create your own global "helper" PHP functions in designated directories, and Apiato will automatically autoload them for you.

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Notifications

    Apiato notifications are just Laravel Notifications,

    Policies

    Apiato policies are just Laravel Policies,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Seeders

    Apiato seeders are just Laravel Seeders,

    Tests

    Apiato is built with testing in mind.

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/tags/policy/index.html b/docs/tags/policy/index.html index a8478302b..178bff5d9 100644 --- a/docs/tags/policy/index.html +++ b/docs/tags/policy/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "policy" | Apiato - +

    2 docs tagged with "policy"

    View All Tags

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Policies

    Apiato policies are just Laravel Policies,

    - + \ No newline at end of file diff --git a/docs/tags/porto/index.html b/docs/tags/porto/index.html index c167d7296..1e7d5763e 100644 --- a/docs/tags/porto/index.html +++ b/docs/tags/porto/index.html @@ -4,13 +4,13 @@ 5 docs tagged with "porto" | Apiato - +

    5 docs tagged with "porto"

    View All Tags

    Container

    Containers are at the core of Apiato.

    Porto

    Porto is a modern software architectural pattern that offers developers a comprehensive set of guidelines,

    Request Lifecycle

    When using any tool in the "real world", you feel more confident if you understand how that tool works.

    - + \ No newline at end of file diff --git a/docs/tags/profiler/index.html b/docs/tags/profiler/index.html index 589c38a3f..c6bca6a94 100644 --- a/docs/tags/profiler/index.html +++ b/docs/tags/profiler/index.html @@ -4,13 +4,13 @@ One doc tagged with "profiler" | Apiato - +

    One doc tagged with "profiler"

    View All Tags

    Profiler

    Profiling is a crucial aspect of optimizing your application's performance

    - + \ No newline at end of file diff --git a/docs/tags/queue/index.html b/docs/tags/queue/index.html index 198bc4cd6..58ed50d7f 100644 --- a/docs/tags/queue/index.html +++ b/docs/tags/queue/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "queue" | Apiato - +

    3 docs tagged with "queue"

    View All Tags

    Jobs

    Apiato jobs are just Laravel Jobs,

    Mail

    Apiato mails are just Laravel Mails,

    Notifications

    Apiato notifications are just Laravel Notifications,

    - + \ No newline at end of file diff --git a/docs/tags/rate-limiting/index.html b/docs/tags/rate-limiting/index.html index d2ab6626a..f99fdf78c 100644 --- a/docs/tags/rate-limiting/index.html +++ b/docs/tags/rate-limiting/index.html @@ -4,13 +4,13 @@ One doc tagged with "rate-limiting" | Apiato - +

    One doc tagged with "rate-limiting"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/tags/repository/index.html b/docs/tags/repository/index.html index e2351603a..703a1d297 100644 --- a/docs/tags/repository/index.html +++ b/docs/tags/repository/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "repository" | Apiato - +

    3 docs tagged with "repository"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Models

    Models are responsible for representing the data of the application

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    - + \ No newline at end of file diff --git a/docs/tags/request/index.html b/docs/tags/request/index.html index 73187b096..3a4e20dbc 100644 --- a/docs/tags/request/index.html +++ b/docs/tags/request/index.html @@ -4,13 +4,13 @@ 8 docs tagged with "request" | Apiato - +

    8 docs tagged with "request"

    View All Tags

    Actions

    Actions serve as the embodiment of the application's Use Cases,

    Authorization

    Apiato provides a Role-Based Access Control (RBAC) through its Authorization Container.

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Policies

    Apiato policies are just Laravel Policies,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    - + \ No newline at end of file diff --git a/docs/tags/response/index.html b/docs/tags/response/index.html index dcda95d84..6e176fc53 100644 --- a/docs/tags/response/index.html +++ b/docs/tags/response/index.html @@ -4,13 +4,13 @@ One doc tagged with "response" | Apiato - +

    One doc tagged with "response"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/tags/role-based-access-control/index.html b/docs/tags/role-based-access-control/index.html index 48d333f5e..992835f2e 100644 --- a/docs/tags/role-based-access-control/index.html +++ b/docs/tags/role-based-access-control/index.html @@ -4,13 +4,13 @@ One doc tagged with "role-based-access-control" | Apiato - +

    One doc tagged with "role-based-access-control"

    View All Tags
    - + \ No newline at end of file diff --git a/docs/tags/route/index.html b/docs/tags/route/index.html index 1a39279e3..a36cf16dc 100644 --- a/docs/tags/route/index.html +++ b/docs/tags/route/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "route" | Apiato - +

    4 docs tagged with "route"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    Requests

    Requests components are a way to interact with the current HTTP request

    Routes

    Routes are responsible for mapping incoming HTTP requests to their corresponding controller functions.

    - + \ No newline at end of file diff --git a/docs/tags/seeder/index.html b/docs/tags/seeder/index.html index 379af3ba8..31aa62de9 100644 --- a/docs/tags/seeder/index.html +++ b/docs/tags/seeder/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "seeder" | Apiato - +

    3 docs tagged with "seeder"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    Migrations

    Apiato migrations are just Laravel Migrations,

    Seeders

    Apiato seeders are just Laravel Seeders,

    - + \ No newline at end of file diff --git a/docs/tags/service-provider/index.html b/docs/tags/service-provider/index.html index 21b6acba0..5a4aa24d5 100644 --- a/docs/tags/service-provider/index.html +++ b/docs/tags/service-provider/index.html @@ -4,13 +4,13 @@ 3 docs tagged with "service-provider" | Apiato - +

    3 docs tagged with "service-provider"

    View All Tags

    Events

    Apiato events are just Laravel Events,

    Middlewares

    Apiato middlewares are just Laravel Middlewares,

    - + \ No newline at end of file diff --git a/docs/tags/sub-action/index.html b/docs/tags/sub-action/index.html index 543cebfca..2b25aa7e4 100644 --- a/docs/tags/sub-action/index.html +++ b/docs/tags/sub-action/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "sub-action" | Apiato - +

    2 docs tagged with "sub-action"

    View All Tags

    Sub Actions

    SubActions are designed to eliminate code duplication within Actions.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/tags/task/index.html b/docs/tags/task/index.html index fdf782b2d..70b49e6bd 100644 --- a/docs/tags/task/index.html +++ b/docs/tags/task/index.html @@ -4,13 +4,13 @@ 4 docs tagged with "task" | Apiato - +

    4 docs tagged with "task"

    View All Tags

    Criterias

    To prevent overlap with the L5 Repository documentation, this page

    Events

    Apiato events are just Laravel Events,

    Repositories

    Apiato provides a powerful repository pattern implementation based on the L5 Repository package.

    Tasks

    Tasks are specialized classes that hold shared business logic,

    - + \ No newline at end of file diff --git a/docs/tags/test/index.html b/docs/tags/test/index.html index 9a7ca8af2..bca01e3bc 100644 --- a/docs/tags/test/index.html +++ b/docs/tags/test/index.html @@ -4,13 +4,13 @@ One doc tagged with "test" | Apiato - +

    One doc tagged with "test"

    View All Tags

    Tests

    Apiato is built with testing in mind.

    - + \ No newline at end of file diff --git a/docs/tags/testing/index.html b/docs/tags/testing/index.html index 01d7398eb..111f1da1d 100644 --- a/docs/tags/testing/index.html +++ b/docs/tags/testing/index.html @@ -4,13 +4,13 @@ One doc tagged with "testing" | Apiato - +

    One doc tagged with "testing"

    View All Tags

    Factories

    Apiato factories are just Laravel Factories,

    - + \ No newline at end of file diff --git a/docs/tags/transformer/index.html b/docs/tags/transformer/index.html index f435de69a..dade78abc 100644 --- a/docs/tags/transformer/index.html +++ b/docs/tags/transformer/index.html @@ -4,13 +4,13 @@ 2 docs tagged with "transformer" | Apiato - +

    2 docs tagged with "transformer"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    - + \ No newline at end of file diff --git a/docs/tags/value/index.html b/docs/tags/value/index.html index b0dfe628d..c182b13ee 100644 --- a/docs/tags/value/index.html +++ b/docs/tags/value/index.html @@ -4,13 +4,13 @@ One doc tagged with "value" | Apiato - +

    One doc tagged with "value"

    View All Tags

    Values

    Value Objects are short names for known "Value Objects",

    - + \ No newline at end of file diff --git a/docs/tags/view/index.html b/docs/tags/view/index.html index acee8ae3f..5d61ca0e0 100644 --- a/docs/tags/view/index.html +++ b/docs/tags/view/index.html @@ -4,13 +4,13 @@ One doc tagged with "view" | Apiato - +

    One doc tagged with "view"

    View All Tags

    Controllers

    Controllers are tasked with two primary responsibilities:

    - + \ No newline at end of file diff --git a/index.html b/index.html index 448f6abfc..6315bf7c4 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ Apiato - +

    Unearth Scale, Ignite Speed

    Conquer Complexity

    Apiato stands as a scalable and enterprise-grade framework layered atop Laravel, addressing a crucial gap in the development of expansive applications: the Architecture.

    Features

    Detailed Documentation, with Examples, Definitions, Principles and Guidelines.

    Powerful Code Generator

    API Documentations Generator
    (using APIDocJS)

    API Versioning

    OAuth2.0 Authentication
    (using Laravel Passport)

    Hash ID Support

    Role-Based Access Control (RBAC)
    (using Laravel Permission)

    Query Parameters
    (orderBy, sortedBy, etc...)

    Pagination, Limit & Offset

    Data Caching

    Shallow ETag Support

    Performance Profiler

    Localization

    Social Authentication
    (using Laravel Socialite)

    Useful Tests Helpers

    Multiple Response Payload Formats
    (JSON, Data Array & Pure Data)

    - + \ No newline at end of file